diff --git a/.github/ISSUE_TEMPLATE/bump_dependencies.md b/.github/ISSUE_TEMPLATE/bump_dependencies.md index cbfe49f302..6c1e777a1a 100644 --- a/.github/ISSUE_TEMPLATE/bump_dependencies.md +++ b/.github/ISSUE_TEMPLATE/bump_dependencies.md @@ -12,6 +12,7 @@ assignees: '' Update `nwaku` "vendor" dependencies. ### Items to bump +- [ ] negentropy - [ ] dnsclient.nim ( update to the latest tag version ) - [ ] nim-bearssl - [ ] nimbus-build-system diff --git a/.github/ISSUE_TEMPLATE/prepare_release.md b/.github/ISSUE_TEMPLATE/prepare_release.md index 03342c45f8..666bff4e0f 100644 --- a/.github/ISSUE_TEMPLATE/prepare_release.md +++ b/.github/ISSUE_TEMPLATE/prepare_release.md @@ -20,6 +20,7 @@ All items below are to be completed by the owner of the given release. - [ ] Create release branch - [ ] Assign release candidate tag to the release branch HEAD. e.g. v0.30.0-rc.0 - [ ] Generate and edit releases notes in CHANGELOG.md +- [ ] Review possible update of [config-options](https://github.com/waku-org/docs.waku.org/blob/develop/docs/guides/nwaku/config-options.md) - [ ] _End user impact_: Summarize impact of changes on Status end users (can be a comment in this issue). - [ ] **Validate release candidate** @@ -43,7 +44,7 @@ All items below are to be completed by the owner of the given release. - [ ] 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 _end user impact_ - - [ ] Inform other (Waku and Status) CCs to point their instance to `status.staging` for a few days. Ping Status colleagues from their Discord server or Status community (not blocking point.) + - [ ] Inform other (Waku and Status) CCs to point their instance to `status.staging` for a few days. Ping Status colleagues from their Discord server or [Status community](https://status.app/c/G3kAAMSQtb05kog3aGbr3kiaxN4tF5xy4BAGEkkLwILk2z3GcoYlm5hSJXGn7J3laft-tnTwDWmYJ18dP_3bgX96dqr_8E3qKAvxDf3NrrCMUBp4R9EYkQez9XSM4486mXoC3mIln2zc-TNdvjdfL9eHVZ-mGgs=#zQ3shZeEJqTC1xhGUjxuS4rtHSrhJ8vUYp64v6qWkLpvdy9L9) (not blocking point.) - [ ] Ask Status-QA to perform sanity checks (as described above) + checks based on _end user impact_; do specify the version being tested - [ ] Ask Status-QA or infra to run the automated Status e2e tests against `status.staging` - [ ] Get other CCs sign-off: they comment on this PR "used app for a week, no problem", or problem reported, resolved and new RC diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 464a2fe8e4..43f3cc15f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: export MAKEFLAGS="-j1" export NIMFLAGS="--colors:off -d:chronicles_colors:none" - + make V=1 LOG_LEVEL=DEBUG QUICK_AND_DIRTY_COMPILER=1 POSTGRES=$postgres_enabled test testwakunode2 build-docker-image: @@ -141,25 +141,36 @@ jobs: nim_wakunode_image: ${{ needs.build-docker-image.outputs.image }} test_type: node-optional debug: waku* - + lint: name: "Lint" runs-on: ubuntu-latest + needs: build steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get submodules hash + id: submodules + run: | + echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT + + - name: Cache submodules + uses: actions/cache@v3 with: - fetch-depth: 2 # In PR, has extra merge commit: ^1 = PR, ^2 = base + path: | + vendor/ + .git/modules + key: ${{ runner.os }}-vendor-modules-${{ steps.submodules.outputs.hash }} + + - name: Build nph + run: | + make build-nph - name: Check nph formatting - # Pin nph to a specific version to avoid sudden style differences. - # Updating nph version should be accompanied with running the new - # version on the fluffy directory. run: | - VERSION="v0.5.1" - ARCHIVE="nph-linux_x64.tar.gz" - curl -L "https://github.com/arnetheduck/nph/releases/download/${VERSION}/${ARCHIVE}" -o ${ARCHIVE} - tar -xzf ${ARCHIVE} shopt -s extglob # Enable extended globbing - ./nph examples waku tests tools apps *.@(nim|nims|nimble) + NPH=$(make print-nph-path) + echo "using nph at ${NPH}" + "${NPH}" examples waku tests tools apps *.@(nim|nims|nimble) git diff --exit-code diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index c93e0513bb..aa51212ec7 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -123,7 +123,7 @@ jobs: ref: master - name: download artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 - name: prep variables id: vars diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index bf68e33a9a..819b40a234 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -58,7 +58,7 @@ jobs: tar -cvzf ${{steps.vars.outputs.nwaku}} ./build/ - name: Upload asset - uses: actions/upload-artifact@v2.2.3 + uses: actions/upload-artifact@v4.4.0 with: name: ${{steps.vars.outputs.nwaku}} path: ${{steps.vars.outputs.nwaku}} diff --git a/.gitmodules b/.gitmodules index 57ddea4d42..5650701b4c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -169,3 +169,13 @@ url = https://github.com/nim-lang/db_connector.git ignore = untracked branch = master +[submodule "vendor/negentropy"] + ignore = untracked + path = vendor/negentropy + url = https://github.com/waku-org/negentropy.git + branch = master +[submodule "vendor/nph"] + ignore = untracked + branch = master + path = vendor/nph + url = https://github.com/arnetheduck/nph.git diff --git a/CHANGELOG.md b/CHANGELOG.md index e60ce00705..b7a9469180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,87 @@ +## v0.32.0 (2024-08-30) + +#### Notes: + +* A new `discv5-only` CLI flag was introduced, which if set to true will perform optimizations for nodes that only run the DiscV5 service +* The `protected-topic` CLI config item has been deprecated in favor of the new `protected-shard` configuration. Protected topics are still supported and will be completely removed in two releases time for `v0.34.0` + +### Release highlights + +* Merged Nwaku Sync protocol for synchronizing store nodes +* Added Store Resume mechanism to retrieve messages sent when the node was offline + +### Features + +- Nwaku Sync ([#2403](https://github.com/waku-org/nwaku/issues/2403)) ([2cc86c51](https://github.com/waku-org/nwaku/commit/2cc86c51)) +- misc. updates for discovery network analysis ([#2930](https://github.com/waku-org/nwaku/issues/2930)) ([4340eb75](https://github.com/waku-org/nwaku/commit/4340eb75)) +- store resume ([#2919](https://github.com/waku-org/nwaku/issues/2919)) ([aed2a113](https://github.com/waku-org/nwaku/commit/aed2a113)) + +### Bug Fixes + +- return on insert error ([#2956](https://github.com/waku-org/nwaku/issues/2956)) ([5f0fbd78](https://github.com/waku-org/nwaku/commit/5f0fbd78)) +- network monitor improvements ([#2939](https://github.com/waku-org/nwaku/issues/2939)) ([80583237](https://github.com/waku-org/nwaku/commit/80583237)) +- add back waku discv5 metrics ([#2927](https://github.com/waku-org/nwaku/issues/2927)) ([e4e01fab](https://github.com/waku-org/nwaku/commit/e4e01fab)) +- update and shift unittest ([#2934](https://github.com/waku-org/nwaku/issues/2934)) ([08973add](https://github.com/waku-org/nwaku/commit/08973add)) +- handle rln-relay-message-limit ([#2867](https://github.com/waku-org/nwaku/issues/2867)) ([8d107b0d](https://github.com/waku-org/nwaku/commit/8d107b0d)) + +### Changes + +- libwaku retrieve my enr and adapt golang example ([#2987](https://github.com/waku-org/nwaku/issues/2987)) ([1ff9f1dd](https://github.com/waku-org/nwaku/commit/1ff9f1dd)) +- run `ANALYZE messages` regularly for better db performance ([#2986](https://github.com/waku-org/nwaku/issues/2986)) ([32f2d85d](https://github.com/waku-org/nwaku/commit/32f2d85d)) +- liteprotocoltester for simulation and for fleets ([#2813](https://github.com/waku-org/nwaku/issues/2813)) ([f4fa73e9](https://github.com/waku-org/nwaku/commit/f4fa73e9)) +- lock in nph version and add pre-commit hook ([#2938](https://github.com/waku-org/nwaku/issues/2938)) ([d63e3430](https://github.com/waku-org/nwaku/commit/d63e3430)) +- logging received message info via onValidated observer ([#2973](https://github.com/waku-org/nwaku/issues/2973)) ([e8bce67d](https://github.com/waku-org/nwaku/commit/e8bce67d)) +- deprecating protected topics in favor of protected shards ([#2983](https://github.com/waku-org/nwaku/issues/2983)) ([e51ffe07](https://github.com/waku-org/nwaku/commit/e51ffe07)) +- rename NsPubsubTopic ([#2974](https://github.com/waku-org/nwaku/issues/2974)) ([67439057](https://github.com/waku-org/nwaku/commit/67439057)) +- install dig ([#2975](https://github.com/waku-org/nwaku/issues/2975)) ([d24b56b9](https://github.com/waku-org/nwaku/commit/d24b56b9)) +- print WakuMessageHash as hex strings ([#2969](https://github.com/waku-org/nwaku/issues/2969)) ([2fd4eb62](https://github.com/waku-org/nwaku/commit/2fd4eb62)) +- updating dependencies for release 0.32.0 ([#2971](https://github.com/waku-org/nwaku/issues/2971)) ([dfd42a7c](https://github.com/waku-org/nwaku/commit/dfd42a7c)) +- bump negentropy to latest master ([#2968](https://github.com/waku-org/nwaku/issues/2968)) ([b36cb075](https://github.com/waku-org/nwaku/commit/b36cb075)) +- keystore: verbose error message when credential is not found ([#2943](https://github.com/waku-org/nwaku/issues/2943)) ([0f11ee14](https://github.com/waku-org/nwaku/commit/0f11ee14)) +- upgrade peer exchange mounting ([#2953](https://github.com/waku-org/nwaku/issues/2953)) ([42f1bed0](https://github.com/waku-org/nwaku/commit/42f1bed0)) +- replace statusim.net instances with status.im ([#2941](https://github.com/waku-org/nwaku/issues/2941)) ([f534549a](https://github.com/waku-org/nwaku/commit/f534549a)) +- updating doc reference to https rpc ([#2937](https://github.com/waku-org/nwaku/issues/2937)) ([bb7bba35](https://github.com/waku-org/nwaku/commit/bb7bba35)) +- Simplification of store legacy code ([#2931](https://github.com/waku-org/nwaku/issues/2931)) ([d4e8a0da](https://github.com/waku-org/nwaku/commit/d4e8a0da)) +- add peer filtering by cluster for waku peer exchange ([#2932](https://github.com/waku-org/nwaku/issues/2932)) ([b4618f98](https://github.com/waku-org/nwaku/commit/b4618f98)) +- return all connected peers from REST API ([#2923](https://github.com/waku-org/nwaku/issues/2923)) ([a29eca77](https://github.com/waku-org/nwaku/commit/a29eca77)) +- adding lint job to the CI ([#2925](https://github.com/waku-org/nwaku/issues/2925)) ([086cc8ed](https://github.com/waku-org/nwaku/commit/086cc8ed)) +- improve sonda dashboard ([#2918](https://github.com/waku-org/nwaku/issues/2918)) ([6d385cef](https://github.com/waku-org/nwaku/commit/6d385cef)) +- Add new custom built and test target to make in order to enable easy build or test single nim modules ([#2913](https://github.com/waku-org/nwaku/issues/2913)) ([ad25f437](https://github.com/waku-org/nwaku/commit/ad25f437)) + +This release supports the following [libp2p protocols](https://docs.libp2p.io/concepts/protocols/): +| Protocol | Spec status | Protocol id | +| ---: | :---: | :--- | +| [`11/WAKU2-RELAY`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/11/relay.md) | `stable` | `/vac/waku/relay/2.0.0` | +| [`12/WAKU2-FILTER`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/12/filter.md) | `draft` | `/vac/waku/filter/2.0.0-beta1`
`/vac/waku/filter-subscribe/2.0.0-beta1`
`/vac/waku/filter-push/2.0.0-beta1` | +| [`13/WAKU2-STORE`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/13/store.md) | `draft` | `/vac/waku/store/2.0.0-beta4` | +| [`19/WAKU2-LIGHTPUSH`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/19/lightpush.md) | `draft` | `/vac/waku/lightpush/2.0.0-beta1` | +| [`66/WAKU2-METADATA`](https://github.com/waku-org/specs/blob/master/standards/core/metadata.md) | `raw` | `/vac/waku/metadata/1.0.0` | +| [`WAKU-SYNC`](https://github.com/waku-org/specs/blob/feat--waku-sync/standards/core/sync.md) | `draft` | `/vac/waku/sync/1.0.0` | + +## v0.31.1 (2024-08-02) + +### Changes + +- Optimize hash queries with lookup table ([#2933](https://github.com/waku-org/nwaku/issues/2933)) ([6463885bf](https://github.com/waku-org/nwaku/commit/6463885bf)) + +### Bug fixes + +* Use of detach finalize when needed [2966](https://github.com/waku-org/nwaku/pull/2966) +* Prevent legacy store from creating new partitions as that approach blocked the database. +[2931](https://github.com/waku-org/nwaku/pull/2931) + +* lightpush better feedback in case the lightpush service node does not have peers [2951](https://github.com/waku-org/nwaku/pull/2951) + +This release supports the following [libp2p protocols](https://docs.libp2p.io/concepts/protocols/): +| Protocol | Spec status | Protocol id | +| ---: | :---: | :--- | +| [`11/WAKU2-RELAY`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/11/relay.md) | `stable` | `/vac/waku/relay/2.0.0` | +| [`12/WAKU2-FILTER`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/12/filter.md) | `draft` | `/vac/waku/filter/2.0.0-beta1`
`/vac/waku/filter-subscribe/2.0.0-beta1`
`/vac/waku/filter-push/2.0.0-beta1` | +| [`WAKU2-STORE`](https://github.com/waku-org/specs/blob/master/standards/core/store.md) | `draft` | `/vac/waku/store-query/3.0.0` | +| [`13/WAKU2-STORE`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/13/store.md) | `draft` | `/vac/waku/store/2.0.0-beta4` | +| [`19/WAKU2-LIGHTPUSH`](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/19/lightpush.md) | `draft` | `/vac/waku/lightpush/2.0.0-beta1` | +| [`66/WAKU2-METADATA`](https://github.com/waku-org/specs/blob/master/standards/core/metadata.md) | `raw` | `/vac/waku/metadata/1.0.0` | + ## v0.31.0 (2024-07-16) ### Notes diff --git a/Dockerfile b/Dockerfile index 2667af3968..181a6a6314 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ARG NIM_COMMIT ARG LOG_LEVEL=TRACE # Get build tools and required header files -RUN apk add --no-cache bash git build-base pcre-dev linux-headers curl jq +RUN apk add --no-cache bash git build-base openssl-dev pcre-dev linux-headers curl jq WORKDIR /app COPY . . @@ -41,7 +41,7 @@ LABEL version="unknown" EXPOSE 30303 60000 8545 # Referenced in the binary -RUN apk add --no-cache libgcc pcre-dev libpq-dev +RUN apk add --no-cache libgcc pcre-dev libpq-dev bind-tools # Fix for 'Error loading shared library libpcre.so.3: No such file or directory' RUN ln -s /usr/lib/libpcre.so /usr/lib/libpcre.so.3 diff --git a/Makefile b/Makefile index eeb44c71a5..bf6f9b3861 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,8 @@ BUILD_SYSTEM_DIR := vendor/nimbus-build-system EXCLUDED_NIM_PACKAGES := vendor/nim-dnsdisc/vendor LINK_PCRE := 0 LOG_LEVEL := TRACE - +NPH := vendor/nph/src/nph +FORMAT_MSG := "\\x1B[95mFormatting:\\x1B[39m" # we don't want an error here, so we can handle things later, in the ".DEFAULT" target -include $(BUILD_SYSTEM_DIR)/makefiles/variables.mk @@ -98,7 +99,7 @@ ifeq (, $(shell which cargo)) endif anvil: rustup -ifeq (, $(shell which anvil)) +ifeq (, $(shell which anvil 2> /dev/null)) # Install Anvil if it's not installed ./scripts/install_anvil.sh endif @@ -165,6 +166,34 @@ clean-librln: # Extend clean target clean: | clean-librln +###################### +### NEGENTROPY ### +###################### +.PHONY: negentropy + +LIBNEGENTROPY_BUILDDIR := $(CURDIR)/vendor/negentropy/cpp +LIBNEGENTROPY_FILE := libnegentropy.a + +deps: | negentropy + +clean: | negentropy-clean + +$(LIBNEGENTROPY_FILE): + $(MAKE) -C $(LIBNEGENTROPY_BUILDDIR) && \ + cp $(LIBNEGENTROPY_BUILDDIR)/${LIBNEGENTROPY_FILE} ${LIBNEGENTROPY_FILE} + +negentropy: | $(LIBNEGENTROPY_FILE) + ## Pass libnegentropy and it's deps to linker. + $(eval LIBNEGENTROPY_PATH := $(shell if [ -f "$(LIBNEGENTROPY_FILE)" ]; then echo "$(LIBNEGENTROPY_FILE)"; else echo "./$(LIBNEGENTROPY_FILE)"; fi)) + $(eval NIM_PARAMS += --passL:$(LIBNEGENTROPY_PATH) --passL:-lcrypto --passL:-lssl --passL:-lstdc++) +ifeq ($(detected_OS),Darwin) + $(eval NIM_PARAMS += --passL:-L/opt/homebrew/lib/) +endif + +negentropy-clean: + $(MAKE) -C $(LIBNEGENTROPY_BUILDDIR) clean && \ + rm ${LIBNEGENTROPY_FILE} + ################# ## Waku Common ## @@ -241,6 +270,36 @@ networkmonitor: | build deps librln echo -e $(BUILD_MSG) "build/$@" && \ $(ENV_SCRIPT) nim networkmonitor $(NIM_PARAMS) waku.nims +############ +## Format ## +############ +.PHONY: build-nph install-nph clean-nph print-nph-path + +build-nph: + $(ENV_SCRIPT) nim c vendor/nph/src/nph.nim + +GIT_PRE_COMMIT_HOOK := .git/hooks/pre-commit + +install-nph: build-nph +ifeq ("$(wildcard $(GIT_PRE_COMMIT_HOOK))","") + cp ./scripts/git_pre_commit_format.sh $(GIT_PRE_COMMIT_HOOK) +else + echo "$(GIT_PRE_COMMIT_HOOK) already present, will NOT override" + exit 1 +endif + +nph/%: build-nph + echo -e $(FORMAT_MSG) "nph/$*" && \ + $(NPH) $* + +clean-nph: + rm -f $(NPH) + +# To avoid hardcoding nph binary location in several places +print-nph-path: + echo "$(NPH)" + +clean: | clean-nph ################### ## Documentation ## @@ -373,6 +432,7 @@ cwaku_example: | build libwaku ./examples/cbindings/base64.c \ -lwaku -Lbuild/ \ -pthread -ldl -lm \ + -lnegentropy -Lvendor/negentropy/cpp/ \ -lminiupnpc -Lvendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build/ \ -lnatpmp -Lvendor/nim-nat-traversal/vendor/libnatpmp-upstream/ \ vendor/nim-libbacktrace/libbacktrace_wrapper.o \ @@ -385,6 +445,7 @@ cppwaku_example: | build libwaku ./examples/cpp/base64.cpp \ -lwaku -Lbuild/ \ -pthread -ldl -lm \ + -lnegentropy -Lvendor/negentropy/cpp/ \ -lminiupnpc -Lvendor/nim-nat-traversal/vendor/miniupnp/miniupnpc/build/ \ -lnatpmp -Lvendor/nim-nat-traversal/vendor/libnatpmp-upstream/ \ vendor/nim-libbacktrace/libbacktrace_wrapper.o \ @@ -411,3 +472,4 @@ release-notes: sed -E 's@#([0-9]+)@[#\1](https://github.com/waku-org/nwaku/issues/\1)@g' # I could not get the tool to replace issue ids with links, so using sed for now, # asked here: https://github.com/bvieira/sv4git/discussions/101 + diff --git a/README.md b/README.md index 5b0ac7d4c4..5fb8afc1e2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The nwaku repository implements Waku, and provides tools related to it. - Examples of Waku usage. - Various tests of above. -For more details see the [source code](waku/v2/README.md) +For more details see the [source code](waku/README.md) ## How to Build & Run @@ -53,7 +53,8 @@ If you encounter difficulties building the project on WSL, consider placing the #### Nim Runtime This repository is bundled with a Nim runtime that includes the necessary dependencies for the project. -Before you can utilise the runtime you'll need to build the project, as detailed in a previous section. This will generate a `vendor` directory containing various dependencies, including the `nimbus-build-system` which has the bundled nim runtime. +Before you can utilise the runtime you'll need to build the project, as detailed in a previous section. +This will generate a `vendor` directory containing various dependencies, including the `nimbus-build-system` which has the bundled nim runtime. After successfully building the project, you may bring the bundled runtime into scope by running: ```bash @@ -84,6 +85,24 @@ Binary will be created as `.bin` under the `build` d make test/tests/common/test_enr_builder.nim ``` +## Formatting + +Nim files are expected to be formatted using the [`nph`](https://github.com/arnetheduck/nph) version present in `vendor/nph`. + +You can easily format file with the `make nph/ file` command. +For example: + +``` +make nph/waku/waku_core.nim +``` + +A convenient git hook is provided to automatically format file at commit time. +Run the following command to install it: + +```shell +make install-nph +``` + ### Examples Examples can be found in the examples folder. diff --git a/apps/chat2/chat2.nim b/apps/chat2/chat2.nim index dd2694cf77..f844deada7 100644 --- a/apps/chat2/chat2.nim +++ b/apps/chat2/chat2.nim @@ -6,7 +6,7 @@ when not (compileOption("threads")): {.push raises: [].} -import std/[strformat, strutils, times, options, random] +import std/[strformat, strutils, times, options, random, sequtils] import confutils, chronicles, @@ -379,7 +379,9 @@ proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} = raise newException(ConfigurationError, "rln-relay-cred-path MUST be passed") if conf.relay: - await node.mountRelay(conf.topics.split(" ")) + let shards = + conf.shards.mapIt(RelayShard(clusterId: conf.clusterId, shardId: uint16(it))) + await node.mountRelay(shards) await node.mountLibp2pPing() diff --git a/apps/chat2/config_chat2.nim b/apps/chat2/config_chat2.nim index 417d298a3f..5086aef51b 100644 --- a/apps/chat2/config_chat2.nim +++ b/apps/chat2/config_chat2.nim @@ -83,11 +83,19 @@ type name: "keep-alive" .}: bool - topics* {. - desc: "Default topics to subscribe to (space separated list).", - defaultValue: "/waku/2/rs/0/0", - name: "topics" - .}: string + clusterId* {. + desc: + "Cluster id that the node is running in. Node in a different cluster id is disconnected.", + defaultValue: 0, + name: "cluster-id" + .}: uint16 + + shards* {. + desc: + "Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.", + defaultValue: @[uint16(0)], + name: "shard" + .}: seq[uint16] ## Store config store* {. diff --git a/apps/chat2bridge/config_chat2bridge.nim b/apps/chat2bridge/config_chat2bridge.nim index c858fdb3b2..c7d8bb56a8 100644 --- a/apps/chat2bridge/config_chat2bridge.nim +++ b/apps/chat2bridge/config_chat2bridge.nim @@ -67,12 +67,6 @@ type Chat2MatterbridgeConf* = object name: "nodekey" .}: crypto.PrivateKey - topics* {. - desc: "Default topics to subscribe to (space separated list)", - defaultValue: "/waku/2/rs/0/0", - name: "topics" - .}: string - store* {. desc: "Flag whether to start store protocol", defaultValue: true, name: "store" .}: bool diff --git a/apps/liteprotocoltester/.env b/apps/liteprotocoltester/.env new file mode 100644 index 0000000000..0d0d0ef6c4 --- /dev/null +++ b/apps/liteprotocoltester/.env @@ -0,0 +1,27 @@ +START_PUBLISHING_AFTER=45 +# can add some seconds delay before SENDER starts publishing + +NUM_MESSAGES=0 +# 0 for infinite number of messages + +DELAY_MESSAGES=8000 +# ms delay between messages + + +MIN_MESSAGE_SIZE=15Kb +MAX_MESSAGE_SIZE=145Kb + +## for wakusim +#PUBSUB=/waku/2/rs/66/0 +#CONTENT_TOPIC=/tester/2/light-pubsub-test/wakusim +#CLUSTER_ID=66 + +## for status.prod +#PUBSUB=/waku/2/rs/16/32 +#CONTENT_TOPIC=/tester/2/light-pubsub-test/fleet +#CLUSTER_ID=16 + +## for TWN +PUBSUB=/waku/2/rs/1/4 +CONTENT_TOPIC=/tester/2/light-pubsub-test/twn +CLUSTER_ID=1 diff --git a/apps/liteprotocoltester/Dockerfile.liteprotocoltester.copy b/apps/liteprotocoltester/Dockerfile.liteprotocoltester similarity index 84% rename from apps/liteprotocoltester/Dockerfile.liteprotocoltester.copy rename to apps/liteprotocoltester/Dockerfile.liteprotocoltester index 02ef7ad503..11e37078d0 100644 --- a/apps/liteprotocoltester/Dockerfile.liteprotocoltester.copy +++ b/apps/liteprotocoltester/Dockerfile.liteprotocoltester @@ -4,9 +4,9 @@ ## This is used for faster turnaround time for testing the compiled binary. ## Prerequisites: compiled liteprotocoltester binary in build/ directory - FROM ubuntu:noble as prod + FROM ubuntu:noble AS prod - LABEL maintainer="jakub@status.im" + LABEL maintainer="zoltan@status.im" LABEL source="https://github.com/waku-org/nwaku" LABEL description="Lite Protocol Tester: Waku light-client" LABEL commit="unknown" @@ -28,8 +28,9 @@ RUN ln -s /usr/lib/libpcre.so /usr/lib/libpcre.so.3 COPY build/liteprotocoltester /usr/bin/ + COPY apps/liteprotocoltester/run_tester_node.sh /usr/bin/ - ENTRYPOINT ["/usr/bin/liteprotocoltester"] + ENTRYPOINT ["/usr/bin/run_tester_node.sh", "/usr/bin/liteprotocoltester"] # # By default just show help if called without arguments CMD ["--help"] diff --git a/apps/liteprotocoltester/Dockerfile.liteprotocoltester.compile b/apps/liteprotocoltester/Dockerfile.liteprotocoltester.compile index bef14abb54..6e3184c9ac 100644 --- a/apps/liteprotocoltester/Dockerfile.liteprotocoltester.compile +++ b/apps/liteprotocoltester/Dockerfile.liteprotocoltester.compile @@ -4,7 +4,7 @@ ARG NIMFLAGS ARG MAKE_TARGET=liteprotocoltester ARG NIM_COMMIT - ARG LOG_LEVEL=TRACE + ARG LOG_LEVEL=DEBUG # Get build tools and required header files RUN apk add --no-cache bash git build-base pcre-dev linux-headers curl jq @@ -27,7 +27,7 @@ # PRODUCTION IMAGE ------------------------------------------------------------- - FROM alpine:3.18 as prod + FROM alpine:3.18 AS prod ARG MAKE_TARGET=liteprotocoltester diff --git a/apps/liteprotocoltester/README.md b/apps/liteprotocoltester/README.md index dbcc9fa5d0..1e7d45f5cf 100644 --- a/apps/liteprotocoltester/README.md +++ b/apps/liteprotocoltester/README.md @@ -37,6 +37,9 @@ At this stage we can only configure number of messages and fixed frequency of th ### Phase 1 +> NOTICE: This part is obsolate due integration with waku-simulator. +> It needs some rework to make it work again standalone. + Lite Protocol Tester application is built under name `liteprotocoltester` in apps/liteprotocoltester folder. Starting from nwaku repository root: @@ -48,16 +51,106 @@ docker compose up -d docker compose logs -f receivernode ``` +### Phase 2 + +> Integration with waku-simulator! + +- For convenience, integration is done in cooperation with waku-simulator repository, but nothing is tightly coupled. +- waku-simulator must be started separately with its own configuration. +- To enable waku-simulator working without RLN currently a separate branch is to be used. +- When waku-simulator is configured and up and running, lite-protocol-tester composite docker setup can be started. + +```bash + +# Start waku-simulator + +git clone https://github.com/waku-org/waku-simulator.git ../waku-simulator +cd ../waku-simulator +git checkout chore-integrate-liteprotocoltester + +# optionally edit .env file + +docker compose -f docker-compose-norln.yml up -d + +# navigate localhost:30001 to see the waku-simulator dashboard + +cd ../{your-repository} + +make LOG_LEVEL=DEBUG liteprotocoltester + +cd apps/liteprotocoltester + +# optionally edit .env file + +docker compose -f docker-compose-on-simularor.yml build +docker compose -f docker-compose-on-simularor.yml up -d +docker compose -f docker-compose-on-simularor.yml logs -f receivernode +``` +#### Current setup + +- waku-simulator is configured to run with 25 full node +- liteprotocoltester is configured to run with 3 publisher and 1 receiver +- liteprotocoltester is configured to run 1 lightpush service and a filter service node + - light clients are connected accordingly +- publishers will send 250 messages in every 200ms with size between 1KiB and 120KiB +- Notice there is a configurable wait before start publishing messages as it is noticed time is needed for the service nodes to get connected to full nodes from simulator +- light clients will print report on their and the connected service node's connectivity to the network in every 20 secs. + +#### Test monitoring + +Navigate to http://localhost:3033 to see the lite-protocol-tester dashboard. + +### Phase 3 + +> Run independently on a chosen waku fleet + +This option is simple as is just to run the built liteprotocoltester binary with run_tester_node.sh script. + +Syntax: +`./run_tester_node.sh ` + +How to run from you nwaku repository: +```bash +cd ../{your-repository} + +make LOG_LEVEL=DEBUG liteprotocoltester + +cd apps/liteprotocoltester + +# optionally edit .env file + +# run publisher side +./run_tester_node.sh ../../build/liteprotocoltester SENDER [chosen service node address that support lightpush] + +# or run receiver side +./run_tester_node.sh ../../build/liteprotocoltester RECEIVER [chosen service node address that support filter service] +``` + +#### Recommendations + +In order to run on any kind of network, it is recommended to deploy the built `liteprotocoltester` binary with the `.env` file and the `run_tester_node.sh` script to the desired machine. + +Select a lightpush service node and a filter service node from the targeted network, or you can run your own. Note down the selected peers peer_id. + +Run a SENDER role liteprotocoltester and a RECEIVER role one on different terminals. Depending on the test aim, you may want to redirect the output to a file. + +> RECEIVER side will periodically print statistics to standard output. + ## Configure ### Environment variables for docker compose runs | Variable | Description | Default | | ---: | :--- | :--- | -| NUM_MESSAGES | Number of message to publish | 120 | +| NUM_MESSAGES | Number of message to publish, 0 means infinite | 120 | | DELAY_MESSAGES | Frequency of messages in milliseconds | 1000 | -| PUBSUB | Used pubsub_topic for testing | /waku/2/rs/0/0 | +| PUBSUB | Used pubsub_topic for testing | /waku/2/rs/66/0 | | CONTENT_TOPIC | content_topic for testing | /tester/1/light-pubsub-example/proto | +| CLUSTER_ID | cluster_id of the network | 16 | +| START_PUBLISHING_AFTER | Delay in seconds before starting to publish to let service node connected | 5 | +| MIN_MESSAGE_SIZE | Minimum message size in bytes | 1KiB | +| MAX_MESSAGE_SIZE | Maximum message size in bytes | 120KiB | + ### Lite Protocol Tester application cli options @@ -67,7 +160,10 @@ docker compose logs -f receivernode | --service-node| Address of the service node to use for lightpush and/or filter service | - | | --num-messages | Number of message to publish | 120 | | --delay-messages | Frequency of messages in milliseconds | 1000 | -| --pubsub-topic | Used pubsub_topic for testing | /waku/2/rs/0/0 | +| --min-message-size | Minimum message size in bytes | 1KiB | +| --max-message-size | Maximum message size in bytes | 120KiB | +| --start-publishing-after | Delay in seconds before starting to publish to let service node connected in seconds | 5 | +| --pubsub-topic | Used pubsub_topic for testing | /waku/2/default-waku/proto | | --content_topic | content_topic for testing | /tester/1/light-pubsub-example/proto | | --cluster-id | Cluster id for the test | 0 | | --config-file | TOML configuration file to fine tune the light waku node
Note that some configurations (full node services) are not taken into account | - | @@ -82,6 +178,34 @@ docker compose logs -f receivernode ### Docker image notice +#### Building for docker compose runs on simulator or standalone Please note that currently to ease testing and development tester application docker image is based on ubuntu and uses the externally pre-built binary of 'liteprotocoltester'. This speeds up image creation. Another dokcer build file is provided for proper build of boundle image. +> `Dockerfile.liteprotocoltester.copy` will create an image with the binary copied from the build directory. + +> `Dockerfile.liteprotocoltester.compile` will create an image completely compiled from source. This can be quite slow. + +#### Creating standalone runner docker image + +To ease the work with lite-proto-tester, a docker image is possible to build. +With that image it is easy to run the application in a container. + +> `Dockerfile.liteprotocoltester` will create an ubuntu image with the binary copied from the build directory. You need to pre-build the application. + +Here is how to build and run: +```bash +cd +make liteprotocoltester + +cd apps/liteprotocoltester +docker build -t liteprotocoltester:latest -f Dockerfile.liteprotocoltester ../.. + +# alternatively you can push it to a registry + +# edit and adjust .env file to your needs and for the network configuration + +docker run --env-file .env liteprotocoltester:latest RECEIVER + +docker run --env-file .env liteprotocoltester:latest SENDER +``` diff --git a/apps/liteprotocoltester/diagnose_connections.nim b/apps/liteprotocoltester/diagnose_connections.nim new file mode 100644 index 0000000000..d5b2cca0ba --- /dev/null +++ b/apps/liteprotocoltester/diagnose_connections.nim @@ -0,0 +1,96 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import + std/[options, strutils, os, sequtils, net, strformat], + chronicles, + chronos, + metrics, + libbacktrace, + system/ansi_c, + libp2p/crypto/crypto, + confutils, + libp2p/wire + +import + ../../waku/common/logging, + ../../waku/factory/waku, + ../../waku/factory/external_config, + ../../waku/node/health_monitor, + ../../waku/node/waku_metrics, + ../../waku/waku_api/rest/builder as rest_server_builder, + ../../waku/node/peer_manager, + ../../waku/waku_lightpush/common, + ../../waku/waku_relay, + ../../waku/waku_filter_v2, + ../../waku/waku_api/rest/client, + ../../waku/waku_api/rest/admin/client, + ./tester_config, + ./lightpush_publisher, + ./filter_subscriber + +logScope: + topics = "diagnose connections" + +proc logSelfPeersLoop(pm: PeerManager, interval: Duration) {.async.} = + trace "Starting logSelfPeersLoop diagnosis loop" + while true: + let selfLighpushPeers = pm.peerStore.getPeersByProtocol(WakuLightPushCodec) + let selfRelayPeers = pm.peerStore.getPeersByProtocol(WakuRelayCodec) + let selfFilterPeers = pm.peerStore.getPeersByProtocol(WakuFilterSubscribeCodec) + + let printable = catch: + """*------------------------------------------------------------------------------------------* +| Self ({pm.switch.peerInfo}) peers: +*------------------------------------------------------------------------------------------* +| Lightpush peers({selfLighpushPeers.len()}): ${selfLighpushPeers} +*------------------------------------------------------------------------------------------* +| Filter peers({selfFilterPeers.len()}): ${selfFilterPeers} +*------------------------------------------------------------------------------------------* +| Relay peers({selfRelayPeers.len()}): ${selfRelayPeers} +*------------------------------------------------------------------------------------------*""".fmt() + + if printable.isErr(): + echo "Error while printing statistics: " & printable.error().msg + else: + echo printable.get() + + await sleepAsync(interval) + +proc logServiceRelayPeers( + pm: PeerManager, codec: string, interval: Duration +) {.async.} = + trace "Starting service node connectivity diagnosys loop" + while true: + echo "*------------------------------------------------------------------------------------------*" + echo "| Service peer connectivity:" + let selfLighpushPeers = pm.selectPeer(codec) + if selfLighpushPeers.isSome(): + let ma = selfLighpushPeers.get().addrs[0] + var serviceIp = initTAddress(ma).valueOr: + echo "Error while parsing multiaddress: " & $error + continue + + serviceIp.port = Port(8645) + let restClient = newRestHttpClient(initTAddress($serviceIp)) + + let getPeersRes = await restClient.getPeers() + + if getPeersRes.status == 200: + let nrOfPeers = getPeersRes.data.len() + echo "Service node (@" & $ma & ") peers: " & $getPeersRes.data + else: + echo "Error while fetching service node (@" & $ma & ") peers: " & + $getPeersRes.data + else: + echo "No service node peers found" + + echo "*------------------------------------------------------------------------------------------*" + + await sleepAsync(interval) + +proc startPeriodicPeerDiagnostic*(pm: PeerManager, codec: string) {.async.} = + asyncSpawn logSelfPeersLoop(pm, chronos.seconds(60)) + # asyncSpawn logServiceRelayPeers(pm, codec, chronos.seconds(20)) diff --git a/apps/liteprotocoltester/docker-compose-on-simularor.yml b/apps/liteprotocoltester/docker-compose-on-simularor.yml new file mode 100644 index 0000000000..e6002376dc --- /dev/null +++ b/apps/liteprotocoltester/docker-compose-on-simularor.yml @@ -0,0 +1,227 @@ +version: "3.7" +x-logging: &logging + logging: + driver: json-file + options: + max-size: 1000m + +# Environment variable definitions +x-eth-client-address: ð_client_address ${ETH_CLIENT_ADDRESS:-} # Add your ETH_CLIENT_ADDRESS after the "-" + +x-rln-environment: &rln_env + RLN_RELAY_CONTRACT_ADDRESS: ${RLN_RELAY_CONTRACT_ADDRESS:-0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4} + RLN_RELAY_CRED_PATH: ${RLN_RELAY_CRED_PATH:-} # Optional: Add your RLN_RELAY_CRED_PATH after the "-" + RLN_RELAY_CRED_PASSWORD: ${RLN_RELAY_CRED_PASSWORD:-} # Optional: Add your RLN_RELAY_CRED_PASSWORD after the "-" + +x-test-running-conditions: &test_running_conditions + NUM_MESSAGES: ${NUM_MESSAGES:-120} + DELAY_MESSAGES: "${DELAY_MESSAGES:-1000}" + PUBSUB: ${PUBSUB:-/waku/2/rs/66/0} + CONTENT_TOPIC: ${CONTENT_TOPIC:-/tester/2/light-pubsub-test/wakusim} + CLUSTER_ID: ${CLUSTER_ID:-66} + MIN_MESSAGE_SIZE: ${MIN_MESSAGE_SIZE:-1Kb} + MAX_MESSAGE_SIZE: ${MAX_MESSAGE_SIZE:-150Kb} + START_PUBLISHING_AFTER: ${START_PUBLISHING_AFTER:-5} # seconds + + +# Services definitions +services: + lightpush-service: + image: ${NWAKU_IMAGE:-harbor.status.im/wakuorg/nwaku:latest-release} + # ports: + # - 30304:30304/tcp + # - 30304:30304/udp + # - 9005:9005/udp + # - 127.0.0.1:8003:8003 + # - 80:80 #Let's Encrypt + # - 8000:8000/tcp #WSS + # - 127.0.0.1:8645:8645 + <<: + - *logging + environment: + DOMAIN: ${DOMAIN} + RLN_RELAY_CRED_PASSWORD: "${RLN_RELAY_CRED_PASSWORD}" + ETH_CLIENT_ADDRESS: *eth_client_address + EXTRA_ARGS: ${EXTRA_ARGS} + <<: + - *rln_env + - *test_running_conditions + volumes: + - ./run_service_node.sh:/opt/run_service_node.sh:Z + - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z + - ./rln_tree:/etc/rln_tree/:Z + - ./keystore:/keystore:Z + entrypoint: sh + command: + - /opt/run_service_node.sh + - LIGHTPUSH + networks: + - waku-simulator_simulation + + publishernode: + image: waku.liteprotocoltester:latest + build: + context: ../.. + dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester + deploy: + replicas: ${NUM_PUBLISHER_NODES:-3} + # ports: + # - 30304:30304/tcp + # - 30304:30304/udp + # - 9005:9005/udp + # - 127.0.0.1:8003:8003 + # - 80:80 #Let's Encrypt + # - 8000:8000/tcp #WSS + # - 127.0.0.1:8646:8646 + <<: + - *logging + environment: + DOMAIN: ${DOMAIN} + RLN_RELAY_CRED_PASSWORD: "${RLN_RELAY_CRED_PASSWORD}" + ETH_CLIENT_ADDRESS: *eth_client_address + EXTRA_ARGS: ${EXTRA_ARGS} + <<: + - *rln_env + - *test_running_conditions + volumes: + - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z + - ./rln_tree:/etc/rln_tree/:Z + - ./keystore:/keystore:Z + entrypoint: sh + command: + - /usr/bin/run_tester_node.sh + - /usr/bin/liteprotocoltester + - SENDER + - waku-sim + depends_on: + - lightpush-service + configs: + - source: cfg_tester_node.toml + target: config.toml + networks: + - waku-simulator_simulation + + filter-service: + image: ${NWAKU_IMAGE:-harbor.status.im/wakuorg/nwaku:latest-release} + # ports: + # - 30304:30305/tcp + # - 30304:30305/udp + # - 9005:9005/udp + # - 127.0.0.1:8003:8003 + # - 80:80 #Let's Encrypt + # - 8000:8000/tcp #WSS + # - 127.0.0.1:8645:8645 + <<: + - *logging + environment: + DOMAIN: ${DOMAIN} + RLN_RELAY_CRED_PASSWORD: "${RLN_RELAY_CRED_PASSWORD}" + ETH_CLIENT_ADDRESS: *eth_client_address + EXTRA_ARGS: ${EXTRA_ARGS} + <<: + - *rln_env + - *test_running_conditions + volumes: + - ./run_service_node.sh:/opt/run_service_node.sh:Z + - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z + - ./rln_tree:/etc/rln_tree/:Z + - ./keystore:/keystore:Z + entrypoint: sh + command: + - /opt/run_service_node.sh + - FILTER + networks: + - waku-simulator_simulation + + + receivernode: + image: waku.liteprotocoltester:latest + build: + context: ../.. + dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester + deploy: + replicas: ${NUM_RECEIVER_NODES:-1} + # ports: + # - 30304:30304/tcp + # - 30304:30304/udp + # - 9005:9005/udp + # - 127.0.0.1:8003:8003 + # - 80:80 #Let's Encrypt + # - 8000:8000/tcp #WSS + # - 127.0.0.1:8647:8647 + <<: + - *logging + environment: + DOMAIN: ${DOMAIN} + RLN_RELAY_CRED_PASSWORD: "${RLN_RELAY_CRED_PASSWORD}" + ETH_CLIENT_ADDRESS: *eth_client_address + EXTRA_ARGS: ${EXTRA_ARGS} + <<: + - *rln_env + - *test_running_conditions + volumes: + - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z + - ./rln_tree:/etc/rln_tree/:Z + - ./keystore:/keystore:Z + entrypoint: sh + command: + - /usr/bin/run_tester_node.sh + - /usr/bin/liteprotocoltester + - RECEIVER + - waku-sim + depends_on: + - filter-service + - publishernode + configs: + - source: cfg_tester_node.toml + target: config.toml + networks: + - waku-simulator_simulation + + # We have prometheus and grafana defined in waku-simulator already + prometheus: + image: docker.io/prom/prometheus:latest + volumes: + - ./monitoring/prometheus-config.yml:/etc/prometheus/prometheus.yml:Z + command: + - --config.file=/etc/prometheus/prometheus.yml + - --web.listen-address=:9099 + # ports: + # - 127.0.0.1:9090:9090 + restart: on-failure:5 + depends_on: + - filter-service + - lightpush-service + - publishernode + - receivernode + networks: + - waku-simulator_simulation + + grafana: + image: docker.io/grafana/grafana:latest + env_file: + - ./monitoring/configuration/grafana-plugins.env + volumes: + - ./monitoring/configuration/grafana.ini:/etc/grafana/grafana.ini:Z + - ./monitoring/configuration/dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml:Z + - ./monitoring/configuration/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:Z + - ./monitoring/configuration/dashboards:/var/lib/grafana/dashboards/:Z + - ./monitoring/configuration/customizations/custom-logo.svg:/usr/share/grafana/public/img/grafana_icon.svg:Z + - ./monitoring/configuration/customizations/custom-logo.svg:/usr/share/grafana/public/img/grafana_typelogo.svg:Z + - ./monitoring/configuration/customizations/custom-logo.png:/usr/share/grafana/public/img/fav32.png:Z + ports: + - 0.0.0.0:3033:3033 + restart: on-failure:5 + depends_on: + - prometheus + networks: + - waku-simulator_simulation + +configs: + cfg_tester_node.toml: + content: | + max-connections = 100 + +networks: + waku-simulator_simulation: + external: true diff --git a/apps/liteprotocoltester/docker-compose.yml b/apps/liteprotocoltester/docker-compose.yml index ccbe26493a..32f67fea1a 100644 --- a/apps/liteprotocoltester/docker-compose.yml +++ b/apps/liteprotocoltester/docker-compose.yml @@ -16,13 +16,18 @@ x-rln-environment: &rln_env x-test-running-conditions: &test_running_conditions NUM_MESSAGES: ${NUM_MESSAGES:-120} DELAY_MESSAGES: "${DELAY_MESSAGES:-1000}" - PUBSUB: ${PUBSUB:-} - CONTENT_TOPIC: ${CONTENT_TOPIC:-} + PUBSUB: ${PUBSUB:-/waku/2/rs/66/0} + CONTENT_TOPIC: ${CONTENT_TOPIC:-/tester/2/light-pubsub-test/wakusim} + CLUSTER_ID: ${CLUSTER_ID:-66} + MIN_MESSAGE_SIZE: ${MIN_MESSAGE_SIZE:-1Kb} + MAX_MESSAGE_SIZE: ${MAX_MESSAGE_SIZE:-150Kb} + START_PUBLISHING_AFTER: ${START_PUBLISHING_AFTER:-5} # seconds + STANDALONE: ${STANDALONE:-1} # Services definitions services: servicenode: - image: ${NWAKU_IMAGE:-harbor.status.im/wakuorg/nwaku:latest} + image: ${NWAKU_IMAGE:-harbor.status.im/wakuorg/nwaku:latest-release} ports: - 30304:30304/tcp - 30304:30304/udp @@ -40,6 +45,7 @@ services: EXTRA_ARGS: ${EXTRA_ARGS} <<: - *rln_env + - *test_running_conditions volumes: - ./run_service_node.sh:/opt/run_service_node.sh:Z - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z @@ -53,7 +59,7 @@ services: image: waku.liteprotocoltester:latest build: context: ../.. - dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester.copy + dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester ports: # - 30304:30304/tcp # - 30304:30304/udp @@ -73,14 +79,15 @@ services: - *rln_env - *test_running_conditions volumes: - - ./run_tester_node.sh:/opt/run_tester_node.sh:Z - ${CERTS_DIR:-./certs}:/etc/letsencrypt/:Z - ./rln_tree:/etc/rln_tree/:Z - ./keystore:/keystore:Z entrypoint: sh command: - - /opt/run_tester_node.sh + - /usr/bin/run_tester_node.sh + - /usr/bin/liteprotocoltester - SENDER + - servicenode depends_on: - servicenode configs: @@ -91,7 +98,7 @@ services: image: waku.liteprotocoltester:latest build: context: ../.. - dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester.copy + dockerfile: ./apps/liteprotocoltester/Dockerfile.liteprotocoltester ports: # - 30304:30304/tcp # - 30304:30304/udp @@ -117,8 +124,10 @@ services: - ./keystore:/keystore:Z entrypoint: sh command: - - /opt/run_tester_node.sh + - /usr/bin/run_tester_node.sh + - /usr/bin/liteprotocoltester - RECEIVER + - servicenode depends_on: - servicenode - publishernode diff --git a/apps/liteprotocoltester/filter_subscriber.nim b/apps/liteprotocoltester/filter_subscriber.nim index 1e12a27f2e..dca8eb880b 100644 --- a/apps/liteprotocoltester/filter_subscriber.nim +++ b/apps/liteprotocoltester/filter_subscriber.nim @@ -45,6 +45,7 @@ proc maintainSubscription( let pingRes = await wakuNode.wakuFilterClient.ping(filterPeer) if pingRes.isErr(): # No subscription found. Let's subscribe. + error "ping failed.", err = pingRes.error trace "no subscription found. Sending subscribe request" let subscribeRes = await wakuNode.filterSubscribe( @@ -52,10 +53,10 @@ proc maintainSubscription( ) if subscribeRes.isErr(): - trace "subscribe request failed. Quitting.", err = subscribeRes.error + error "subscribe request failed. Quitting.", err = subscribeRes.error break else: - trace "subscribe request successful." + notice "subscribe request successful." else: trace "subscription found." @@ -78,15 +79,19 @@ proc setupAndSubscribe*(wakuNode: WakuNode, conf: LiteProtocolTesterConf) = let pushHandler = proc(pubsubTopic: PubsubTopic, message: WakuMessage) {.async.} = let payloadStr = string.fromBytes(message.payload) let testerMessage = js.Json.decode(payloadStr, ProtocolTesterMessage) + let msgHash = computeMessageHash(pubsubTopic, message).to0xHex - stats.addMessage(testerMessage.sender, testerMessage) + stats.addMessage(testerMessage.sender, testerMessage, msgHash) - trace "message received", + notice "message received", index = testerMessage.index, count = testerMessage.count, startedAt = $testerMessage.startedAt, sinceStart = $testerMessage.sinceStart, - sincePrev = $testerMessage.sincePrev + sincePrev = $testerMessage.sincePrev, + size = $testerMessage.size, + pubsubTopic = pubsubTopic, + hash = msgHash wakuNode.wakuFilterClient.registerPushHandler(pushHandler) @@ -97,7 +102,7 @@ proc setupAndSubscribe*(wakuNode: WakuNode, conf: LiteProtocolTesterConf) = proc(udata: pointer) {.gcsafe.} = stats.echoStats() - if stats.checkIfAllMessagesReceived(): + if conf.numMessages > 0 and waitFor stats.checkIfAllMessagesReceived(): waitFor unsubscribe( wakuNode, remotePeer, conf.pubsubTopics[0], conf.contentTopics[0] ) diff --git a/apps/liteprotocoltester/lightpush_publisher.nim b/apps/liteprotocoltester/lightpush_publisher.nim index 0cafbe8538..ddb2946ca5 100644 --- a/apps/liteprotocoltester/lightpush_publisher.nim +++ b/apps/liteprotocoltester/lightpush_publisher.nim @@ -1,15 +1,32 @@ import - std/strformat, + std/[strformat, sysrand, random, sequtils], system/ansi_c, chronicles, chronos, + chronos/timer as chtimer, stew/byteutils, results, json_serialization as js import - waku/[common/logging, waku_node, node/peer_manager, waku_core, waku_lightpush/client], + waku/[ + common/logging, + waku_node, + node/peer_manager, + waku_core, + waku_lightpush/client, + common/utils/parse_size_units, + ], ./tester_config, - ./tester_message + ./tester_message, + ./lpt_metrics + +randomize() + +type SizeRange* = tuple[min: uint64, max: uint64] + +var RANDOM_PALYLOAD {.threadvar.}: seq[byte] +RANDOM_PALYLOAD = urandom(1024 * 1024) + # 1MiB of random payload to be used to extend message proc prepareMessage( sender: string, @@ -17,7 +34,9 @@ proc prepareMessage( startedAt: TimeStamp, prevMessageAt: var Timestamp, contentTopic: ContentTopic, -): WakuMessage = + size: SizeRange, +): (WakuMessage, uint64) = + var renderSize = rand(size.min .. size.max) let current = getNowInNanosecondTime() let payload = ProtocolTesterMessage( sender: sender, @@ -26,61 +45,116 @@ proc prepareMessage( startedAt: startedAt, sinceStart: current - startedAt, sincePrev: current - prevMessageAt, + size: renderSize, ) prevMessageAt = current let text = js.Json.encode(payload) + let contentPayload = toBytes(text & " \0") + + if renderSize < len(contentPayload).uint64: + renderSize = len(contentPayload).uint64 + + let finalPayload = concat( + contentPayload, RANDOM_PALYLOAD[0 .. renderSize - len(contentPayload).uint64] + ) let message = WakuMessage( - payload: toBytes(text), # content of the message + payload: finalPayload, # content of the message contentTopic: contentTopic, # content topic to publish to ephemeral: true, # tell store nodes to not store it timestamp: current, # current timestamp ) - return message + return (message, renderSize) + +var sentMessages {.threadvar.}: OrderedTable[uint32, tuple[hash: string, relayed: bool]] +var failedToSendCause {.threadvar.}: Table[string, uint32] +var failedToSendCount {.threadvar.}: uint32 +var numMessagesToSend {.threadvar.}: uint32 +var messagesSent {.threadvar.}: uint32 + +proc reportSentMessages() {.async.} = + while true: + await sleepAsync(chtimer.seconds(60)) + let report = catch: + """*----------------------------------------* +| Expected | Sent | Failed | +|{numMessagesToSend+failedToSendCount:>11} |{messagesSent:>11} |{failedToSendCount:>11} | +*----------------------------------------*""".fmt() + + if report.isErr: + echo "Error while printing statistics" + else: + echo report.get() + + echo "*--------------------------------------------------------------------------------------------------*" + echo "| Failur cause | count |" + for (cause, count) in failedToSendCause.pairs: + echo fmt"|{cause:<87}|{count:>10}|" + echo "*--------------------------------------------------------------------------------------------------*" + + echo "*--------------------------------------------------------------------------------------------------*" + echo "| Index | Relayed | Hash |" + for (index, info) in sentMessages.pairs: + echo fmt"|{index:>10}|{info.relayed:<9}| {info.hash:<76}|" + echo "*--------------------------------------------------------------------------------------------------*" + # evere sent message hash should logged once + sentMessages.clear() proc publishMessages( wakuNode: WakuNode, lightpushPubsubTopic: PubsubTopic, lightpushContentTopic: ContentTopic, numMessages: uint32, + messageSizeRange: SizeRange, delayMessages: Duration, ) {.async.} = let startedAt = getNowInNanosecondTime() var prevMessageAt = startedAt - var failedToSendCount: uint32 = 0 + var renderMsgSize = messageSizeRange + # sets some default of min max message size to avoid conflict with meaningful payload size + renderMsgSize.min = max(1024.uint64, renderMsgSize.min) # do not use less than 1KB + renderMsgSize.max = max(2048.uint64, renderMsgSize.max) # minimum of max is 2KB + renderMsgSize.min = min(renderMsgSize.min, renderMsgSize.max) + renderMsgSize.max = max(renderMsgSize.min, renderMsgSize.max) let selfPeerId = $wakuNode.switch.peerInfo.peerId - - var messagesSent: uint32 = 1 - while numMessages >= messagesSent: - let message = prepareMessage( - selfPeerId, messagesSent, numMessages, startedAt, prevMessageAt, - lightpushContentTopic, + failedToSendCount = 0 + numMessagesToSend = if numMessages == 0: uint32.high else: numMessages + messagesSent = 1 + + while numMessagesToSend >= messagesSent: + let (message, msgSize) = prepareMessage( + selfPeerId, messagesSent, numMessagesToSend, startedAt, prevMessageAt, + lightpushContentTopic, renderMsgSize, ) let wlpRes = await wakuNode.lightpushPublish(some(lightpushPubsubTopic), message) + let msgHash = computeMessageHash(lightpushPubsubTopic, message).to0xHex + if wlpRes.isOk(): - info "published message using lightpush", - index = messagesSent, count = numMessages + sentMessages[messagesSent] = (hash: msgHash, relayed: true) + notice "published message using lightpush", + index = messagesSent, + count = numMessagesToSend, + size = msgSize, + pubsubTopic = lightpushPubsubTopic, + hash = msgHash + inc(messagesSent) + lpt_publisher_sent_messages_count.inc() + lpt_publisher_sent_bytes.inc(amount = msgSize.int64) else: - error "failed to publish message using lightpush", err = wlpRes.error + sentMessages[messagesSent] = (hash: msgHash, relayed: false) + failedToSendCause.mgetOrPut(wlpRes.error, 1).inc() + error "failed to publish message using lightpush", + err = wlpRes.error, hash = msgHash inc(failedToSendCount) + lpt_publisher_failed_messages_count.inc(labelValues = [wlpRes.error]) - await sleepAsync(delayMessages) # Publish every 5 seconds - inc(messagesSent) - - let report = catch: - """*----------------------------------------* -| Expected | Sent | Failed | -|{numMessages:>11} |{messagesSent-failedToSendCount-1:>11} |{failedToSendCount:>11} | -*----------------------------------------*""".fmt() + await sleepAsync(delayMessages) - if report.isErr: - echo "Error while printing statistics" - else: - echo report.get() + waitFor reportSentMessages() discard c_raise(ansi_c.SIGTERM) @@ -90,19 +164,30 @@ proc setupAndPublish*(wakuNode: WakuNode, conf: LiteProtocolTesterConf) = return # give some time to receiver side to set up - # TODO: this maybe done in more sphisticated way, though. - let waitTillStartTesting = 5.seconds + let waitTillStartTesting = conf.startPublishingAfter.seconds + + let parsedMinMsgSize = parseMsgSize(conf.minTestMessageSize).valueOr: + error "failed to parse 'min-test-msg-size' param: ", error = error + return + + let parsedMaxMsgSize = parseMsgSize(conf.maxTestMessageSize).valueOr: + error "failed to parse 'max-test-msg-size' param: ", error = error + return info "Sending test messages in", wait = waitTillStartTesting waitFor sleepAsync(waitTillStartTesting) info "Start sending messages to service node using lightpush" + sentMessages.sort(system.cmp) # Start maintaining subscription asyncSpawn publishMessages( wakuNode, conf.pubsubTopics[0], conf.contentTopics[0], conf.numMessages, + (min: parsedMinMsgSize, max: parsedMaxMsgSize), conf.delayMessages.milliseconds, ) + + asyncSpawn reportSentMessages() diff --git a/apps/liteprotocoltester/liteprotocoltester.nim b/apps/liteprotocoltester/liteprotocoltester.nim index 0fb2a9c699..a109a7bb0e 100644 --- a/apps/liteprotocoltester/liteprotocoltester.nim +++ b/apps/liteprotocoltester/liteprotocoltester.nim @@ -18,10 +18,13 @@ import node/health_monitor, node/waku_metrics, waku_api/rest/builder as rest_server_builder, + waku_lightpush/common, + waku_filter_v2, ], ./tester_config, ./lightpush_publisher, - ./filter_subscriber + ./filter_subscriber, + ./diagnose_connections logScope: topics = "liteprotocoltester main" @@ -72,8 +75,7 @@ when isMainModule: wnconf: WakuNodeConf, sources: auto ) {.gcsafe, raises: [ConfigurationError].} = echo "Loading secondary configuration file into WakuNodeConf" - sources.addConfigFile(Toml, configFile) - , + sources.addConfigFile(Toml, configFile), ) except CatchableError: error "Loading Waku configuration failed", error = getCurrentExceptionMsg() @@ -81,9 +83,9 @@ when isMainModule: wakuConf.logLevel = conf.logLevel wakuConf.logFormat = conf.logFormat - wakuConf.staticNodes = @[conf.serviceNode] + wakuConf.staticnodes = @[conf.serviceNode] wakuConf.nat = conf.nat - wakuConf.maxConnections = 100 + wakuConf.maxConnections = 500 wakuConf.restAddress = conf.restAddress wakuConf.restPort = conf.restPort wakuConf.restAllowOrigin = conf.restAllowOrigin @@ -93,6 +95,10 @@ when isMainModule: wakuConf.clusterId = conf.clusterId ## TODO: Depending on the tester needs we might extend here with shards, clusterId, etc... + wakuConf.metricsServer = true + wakuConf.metricsServerAddress = parseIpAddress("0.0.0.0") + wakuConf.metricsServerPort = 8003 + if conf.testFunc == TesterFunctionality.SENDER: wakuConf.lightpushnode = conf.serviceNode else: @@ -103,7 +109,7 @@ when isMainModule: wakuConf.lightpush = false wakuConf.store = false - wakuConf.rest = true + wakuConf.rest = false # NOTE: {.threadvar.} is used to make the global variable GC safe for the closure uses it # It will always be called from main thread anyway. @@ -186,8 +192,12 @@ when isMainModule: info "Node setup complete" if conf.testFunc == TesterFunctionality.SENDER: + waitFor startPeriodicPeerDiagnostic(wakuApp.node.peerManager, WakuLightPushCodec) setupAndPublish(wakuApp.node, conf) else: + waitFor startPeriodicPeerDiagnostic( + wakuApp.node.peerManager, WakuFilterSubscribeCodec + ) setupAndSubscribe(wakuApp.node, conf) runForever() diff --git a/apps/liteprotocoltester/lpt_metrics.nim b/apps/liteprotocoltester/lpt_metrics.nim new file mode 100644 index 0000000000..655ec098c9 --- /dev/null +++ b/apps/liteprotocoltester/lpt_metrics.nim @@ -0,0 +1,30 @@ +## Example showing how a resource restricted client may +## subscribe to messages without relay + +import metrics + +export metrics + +declarePublicGauge lpt_receiver_sender_peer_count, "count of sender peers" + +declarePublicCounter lpt_receiver_received_messages_count, + "number of messages received per peer", ["peer"] + +declarePublicCounter lpt_receiver_received_bytes, + "number of received bytes per peer", ["peer"] + +declarePublicGauge lpt_receiver_missing_messages_count, + "number of missing messages per peer", ["peer"] + +declarePublicCounter lpt_receiver_duplicate_messages_count, + "number of duplicate messages per peer", ["peer"] + +declarePublicGauge lpt_receiver_distinct_duplicate_messages_count, + "number of distinct duplicate messages per peer", ["peer"] + +declarePublicCounter lpt_publisher_sent_messages_count, "number of messages published" + +declarePublicCounter lpt_publisher_failed_messages_count, + "number of messages failed to publish per failure cause", ["cause"] + +declarePublicCounter lpt_publisher_sent_bytes, "number of total bytes sent" diff --git a/apps/liteprotocoltester/monitoring/configuration/dashboards/liter-protocol-test-monitoring.json b/apps/liteprotocoltester/monitoring/configuration/dashboards/liter-protocol-test-monitoring.json new file mode 100644 index 0000000000..96d574cfe0 --- /dev/null +++ b/apps/liteprotocoltester/monitoring/configuration/dashboards/liter-protocol-test-monitoring.json @@ -0,0 +1,1091 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Monitoring of lite-protocol-tester's send/receiver performance and failure counters.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 12, + "title": "Test publisher monitor", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "lpt_receiver_sender_peer_count{instance=\"receivernode:8003\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Number of publisher peers", + "type": "gauge" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 11, + "panels": [], + "title": "Test performance", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Publishing rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.gradientMode", + "value": "hue" + }, + { + "id": "custom.fillOpacity", + "value": 15 + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(job) (lpt_publisher_sent_messages_count_total)", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Total published count", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(job) (rate(lpt_publisher_sent_messages_count_total[$__rate_interval]))", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Publishing rate", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Publishes test messages", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "series", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Received message rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.scaleDistribution", + "value": { + "type": "linear" + } + }, + { + "id": "custom.fillOpacity", + "value": 9 + }, + { + "id": "custom.gradientMode", + "value": "hue" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(instance) (lpt_receiver_received_messages_count_total{instance=\"receivernode:8003\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Total message received", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by(instance) (rate(lpt_receiver_received_messages_count_total{instance=\"receivernode:8003\"}[$__rate_interval]))", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Received message rate", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Received test messages", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "series", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "kbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Test message transfer rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.fillOpacity", + "value": 22 + }, + { + "id": "custom.scaleDistribution", + "value": { + "type": "linear" + } + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(job) (lpt_publisher_sent_bytes_total)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Total received bytes", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(job) (rate(lpt_publisher_sent_bytes_total[$__rate_interval]))", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Test message transfer rate", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Sent bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "series", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "kbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Test message transfer rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.fillOpacity", + "value": 22 + }, + { + "id": "custom.scaleDistribution", + "value": { + "type": "linear" + } + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(instance) (lpt_receiver_received_bytes_total{instance=\"receivernode:8003\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Total received bytes", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "sum by(instance) (rate(lpt_receiver_received_bytes_total{instance=\"receivernode:8003\"}[$__rate_interval]))", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Test message transfer rate", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Received bytes", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 10, + "panels": [], + "title": "Failure statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Published message rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.gradientMode", + "value": "hue" + }, + { + "id": "custom.fillOpacity", + "value": 15 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "lpt_publisher_failed_messages_count_total{instance=\"publishernode:8003\"}", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Failed to publish count", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Failed publish count per cause", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "series", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(instance) (lpt_receiver_duplicate_messages_count_total{instance=\"receivernode:8003\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Total duplicates", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(instance) (lpt_receiver_distinct_duplicate_messages_count{instance=\"receivernode:8003\"})", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Distinct duplicates", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Received duplicated messages", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "series", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 21, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "lpt_receiver_missing_messages_count", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Publisher {{peer}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Not arrived messages", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Liteprotocoltester monitoring", + "uid": "fdw6pgh9odszkd", + "version": 1, + "weekStart": "" +} diff --git a/apps/liteprotocoltester/monitoring/configuration/dashboards/nwaku-monitoring.json b/apps/liteprotocoltester/monitoring/configuration/dashboards/nwaku-monitoring.json deleted file mode 100644 index f269b0aeb8..0000000000 --- a/apps/liteprotocoltester/monitoring/configuration/dashboards/nwaku-monitoring.json +++ /dev/null @@ -1,5776 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 12485, - "graphTooltip": 0, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 45, - "panels": [], - "title": "Waku Node", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 9, - "x": 0, - "y": 1 - }, - "id": 41, - "options": { - "displayMode": "gradient", - "maxVizHeight": 300, - "minVizHeight": 10, - "minVizWidth": 0, - "namePlacement": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "rate(waku_histogram_message_size_bucket[1h])/scalar(rate(waku_histogram_message_size_count[1h]))*100", - "format": "heatmap", - "instant": true, - "legendFormat": "__auto", - "range": false, - "refId": "A" - } - ], - "title": "Message distrubution %/kBytes (Last Hour)", - "type": "bargauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "deckbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 9, - "x": 9, - "y": 1 - }, - "id": 38, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_histogram_message_size_sum[1h])/rate(waku_histogram_message_size_count[1h])", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Average Msg Size (Last Hour)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "deckbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 5, - "x": 9, - "y": 5 - }, - "id": 42, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.75, rate(waku_histogram_message_size_bucket[1h]))", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "75% Percentile (Last hour)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "deckbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 14, - "y": 5 - }, - "id": 39, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, rate(waku_histogram_message_size_bucket[1h]))", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "99% Percentile (Last Hour)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 9, - "x": 0, - "y": 9 - }, - "id": 12, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "(increase(waku_node_messages_total[1m]))/60", - "legendFormat": "{{instance}}", - "range": true, - "refId": "A" - } - ], - "title": "Messages/second", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "deckbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 9, - "x": 9, - "y": 9 - }, - "id": 43, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "9.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "waku_histogram_message_size_sum/waku_histogram_message_size_count", - "format": "heatmap", - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "A" - } - ], - "title": "Average msg size (kBytes)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 16 - }, - "id": 147, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_relay_network_bytes_total{direction=\"in\"}[$__rate_interval])", - "legendFormat": "{{topic}}", - "range": true, - "refId": "A" - } - ], - "title": "Relay traffic per shard (in)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 16 - }, - "id": 148, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_relay_network_bytes_total{direction=\"out\"}[$__rate_interval])", - "legendFormat": "{{topic}}", - "range": true, - "refId": "A" - } - ], - "title": "Relay traffic per shard (out)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 0, - "y": 25 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "builder", - "exemplar": false, - "expr": "waku_version{instance=\"nwaku:8003\"}", - "format": "table", - "instant": true, - "legendFormat": "__auto", - "range": false, - "refId": "A" - } - ], - "title": "Version", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "version" - ] - } - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 3, - "y": 25 - }, - "id": 22, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "libp2p_autonat_reachability_confidence", - "legendFormat": "{{reachability}}", - "range": true, - "refId": "A" - } - ], - "title": "Reachability", - "transformations": [ - { - "id": "reduce", - "options": { - "includeTimeField": false, - "mode": "reduceFields", - "reducers": [ - "max" - ] - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 6, - "y": 25 - }, - "id": 32, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "routing_table_nodes{state=\"seen\"}", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Discv5 (Seen Nodes)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 9, - "y": 25 - }, - "id": 33, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "routing_table_nodes", - "legendFormat": "{{label_name}}", - "range": true, - "refId": "A" - } - ], - "title": "Discv5 (Nodes)", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "Time", - "{__name__=\"routing_table_nodes\", instance=\"nwaku:8003\", job=\"nwaku\"}" - ] - } - } - } - ], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 12, - "y": 25 - }, - "id": 25, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "builder", - "expr": "libp2p_peers", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Connected Peers", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 15, - "y": 25 - }, - "id": 28, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "libp2p_pubsub_topics", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Number Pubsub Topics", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "dateTimeAsIso" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 0, - "y": 30 - }, - "id": 10, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "process_start_time_seconds{job=\"nwaku\"}*1000", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Start Times (UTC)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 15, - "x": 3, - "y": 30 - }, - "id": 44, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "waku_connected_peers", - "legendFormat": "{{direction}}_{{protocol}}", - "range": true, - "refId": "A" - } - ], - "title": "Connected Peers (Direction/Protocol)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 0, - "y": 35 - }, - "id": 36, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "waku_peer_store_size", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Peer Store Size", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 40 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(libp2p_network_bytes_total{direction=\"in\"}[$__rate_interval])", - "legendFormat": "traffic_{{direction}}", - "range": true, - "refId": "A" - } - ], - "title": "libp2p traffic (in)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 40 - }, - "id": 29, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(libp2p_network_bytes_total{direction=\"out\"}[$__rate_interval])", - "legendFormat": "traffic_{{direction}}", - "range": true, - "refId": "A" - } - ], - "title": "libp2p traffic (out)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 6, - "x": 0, - "y": 49 - }, - "id": 4, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "builder", - "expr": "libp2p_peers", - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A" - } - ], - "title": "Connected Peers", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 6, - "y": 49 - }, - "id": 149, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_service_requests_total[$__rate_interval])", - "legendFormat": "{{proto}}/{{state}}", - "range": true, - "refId": "A" - } - ], - "title": "Protocol request rates", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 59 - }, - "id": 150, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_service_network_bytes_total{direction=\"in\"}[$__rate_interval])", - "legendFormat": "{{service}}", - "range": true, - "refId": "A" - } - ], - "title": "Non relay protocol traffic (in)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 59 - }, - "id": 151, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(waku_service_network_bytes_total{direction=\"out\"}[$__rate_interval])", - "legendFormat": "{{service}}", - "range": true, - "refId": "A" - } - ], - "title": "Non relay protocol traffic (out)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 68 - }, - "id": 20, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "nim_gc_heap_instance_occupied_bytes{}", - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A" - } - ], - "title": "Heap allocation", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 68 - }, - "id": 18, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "nim_gc_mem_bytes{}", - "hide": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A" - } - ], - "title": "Nim Memory Usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 12, - "y": 68 - }, - "id": 135, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_membership_insertion_duration_seconds", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Membership Insertion (seconds)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 12, - "y": 74 - }, - "id": 128, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_number_registered_memberships", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Registered Memberships", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 76 - }, - "id": 127, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_proof_generation_duration_seconds", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Proof Generation (seconds)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 76 - }, - "id": 126, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_proof_verification_duration_seconds", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Proof Verification (seconds)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 82 - }, - "id": 134, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_membership_credentials_import_duration_seconds", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Credentials Import (seconds)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 82 - }, - "id": 137, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_messages_total_total", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Messages Total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 12, - "y": 82 - }, - "id": 136, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_proof_verification_total_total", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Proof Verification Total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 88 - }, - "id": 133, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_invalid_messages_total_total", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Invalid Messages Total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 88 - }, - "id": 130, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_spam_messages_total_total", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{__name__}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Spam Messages Total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 12, - "y": 88 - }, - "id": 138, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "waku_rln_invalid_messages_total_total", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "RLN Invalid Messages", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "Number of messages currently stored in the database", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 9, - "x": 0, - "y": 94 - }, - "id": 141, - "options": { - "legend": { - "calcs": [ - "last" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "pg_tb_stats_messages{}", - "instant": false, - "legendFormat": "{{ pubsubtopic }}", - "range": true, - "refId": "A" - } - ], - "title": "# messages per shard", - "type": "timeseries" - }, - { - "datasource": { - "type": "postgres", - "uid": "e5d2e0c2-371d-4178-ac71-edc122fb459c" - }, - "description": "Messages in local database per app name, as extracted from the content topic.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [ - { - "options": { - "/waku/2/rs/1/0": { - "index": 0, - "text": "0" - }, - "/waku/2/rs/1/1": { - "index": 1, - "text": "1" - }, - "/waku/2/rs/1/2": { - "index": 2, - "text": "2" - }, - "/waku/2/rs/1/3": { - "index": 3, - "text": "3" - }, - "/waku/2/rs/1/4": { - "index": 4, - "text": "4" - }, - "/waku/2/rs/1/5": { - "index": 5, - "text": "5" - }, - "/waku/2/rs/1/6": { - "index": 6, - "text": "6" - }, - "/waku/2/rs/1/7": { - "index": 7, - "text": "7" - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "string" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Number of Messages (sum)" - }, - "properties": [ - { - "id": "unit", - "value": "none" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Total Payload Size (sum)" - }, - "properties": [ - { - "id": "unit", - "value": "decbytes" - } - ] - } - ] - }, - "gridPos": { - "h": 11, - "w": 9, - "x": 9, - "y": 94 - }, - "id": 144, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "enablePagination": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "frameIndex": 1, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "postgres", - "uid": "e5d2e0c2-371d-4178-ac71-edc122fb459c" - }, - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "SELECT REGEXP_REPLACE(contenttopic,'^\\/(.+)\\/(\\d+)\\/(.+)\\/(.+)$','\\1') as \"App name\", COUNT(id), pg_column_size(payload)\nFROM messages\nGROUP BY contenttopic, payload", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [ - { - "name": "pubsubtopic", - "type": "functionParameter" - } - ], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "name": "pubsubtopic", - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "messages" - } - ], - "title": "Stored Message by Content Topic App Name", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": {}, - "indexByName": {}, - "renameByName": { - "contenttopic": "App name", - "count": "Number of Messages", - "pg_column_size": "Total Payload Size" - } - } - }, - { - "id": "groupBy", - "options": { - "fields": { - "App name": { - "aggregations": [ - "uniqueValues" - ], - "operation": "groupby" - }, - "Number of Messages": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - }, - "Total Payload Size": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - }, - "pg_column_size": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - } - } - } - }, - { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "desc": true, - "field": "Number of Messages (sum)" - } - ] - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "Number of messages currently stored in the database", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 9, - "x": 0, - "y": 105 - }, - "id": 146, - "options": { - "legend": { - "calcs": [ - "last" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "pg_tb_messages_count{}", - "instant": false, - "interval": "", - "legendFormat": "messages", - "range": true, - "refId": "A" - } - ], - "title": "Unique stored messages (Postgres)", - "type": "timeseries" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "description": "Clients executing Statements.\n\nSource: pg_stat_activity", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 9, - "y": 105 - }, - "id": 23, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(pg_stat_activity_count{state=\"active\",instance=\"$Instance\"})", - "refId": "A" - } - ], - "thresholds": "", - "title": "Active clients (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "dateTimeAsIso" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 13, - "y": 105 - }, - "id": 125, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "pg_postmaster_start_time_seconds*1000", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Postgres start time", - "type": "stat" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 112 - }, - "id": 46, - "panels": [], - "title": "Postgres", - "type": "row" - }, - { - "colorBackground": false, - "colorValue": false, - "datasource": "Prometheus", - "description": "Source: server_version_num", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - } - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 0, - "y": 113 - }, - "id": 11, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(50, 168, 82)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "max(pg_settings_server_version_num)", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "PostgreSQL Version", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "description": "Transactions committed + roolback per minute\n\nSource: pg_stat_database,xact_commit + xact_rollback", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 4, - "y": 113 - }, - "id": 14, - "interval": "", - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum((rate(pg_stat_database_xact_commit{instance=\"$Instance\"}[$Interval])))+sum((rate(pg_stat_database_xact_rollback{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Transaction rate (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "description": "Statements executed per Minute.\n\nSource: pg_stat_statements.calls", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 8, - "y": 113 - }, - "id": 93, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum((rate(pg_stat_statements_calls{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Query rate (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "description": "Source: pg_stat_statements.total_time / pg_stat_statements.calls", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "format": "s", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 12, - "y": 113 - }, - "id": 102, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum((delta(pg_stat_statements_total_time_seconds{instance=\"$Instance\"}[$Interval])))/sum((delta(pg_stat_statements_calls{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Average query runtime (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "decimals": 2, - "description": "Size of all databases in $Instance.\n\nSource: pg_database_size()", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "format": "bytes", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 16, - "y": 113 - }, - "id": 37, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(pg_database_size_bytes{instance=\"$Instance\"})", - "refId": "A" - } - ], - "thresholds": "", - "title": "Total database size (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "description": "Max Replication lag behind master in seconds\n\nOnly available on a standby system.\n\nSource: pg_last_xact_replay_timestamp\n\nUse: pg_stat_replication for Details.", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "format": "s", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 20, - "y": 113 - }, - "id": 84, - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.2", - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "max(pg_replication_lag{instance=\"$Instance\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Max Replication Lag (Postgres)", - "type": "stat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "max" - }, - { - "datasource": "Prometheus", - "description": "Shared buffer hits vs reads from disc", - "fieldConfig": { - "defaults": { - "decimals": 2, - "mappings": [ - { - "id": 0, - "op": "=", - "text": "N/A", - "type": 1, - "value": "null" - } - ], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "semi-dark-red" - }, - { - "color": "semi-dark-yellow", - "value": 80 - }, - { - "color": "semi-dark-green", - "value": 90 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 3, - "x": 0, - "y": 116 - }, - "id": 16, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "expr": "sum(pg_stat_database_blks_hit{instance=~\"$Instance\"})/(sum(pg_stat_database_blks_hit{instance=~\"$Instance\"})+sum(pg_stat_database_blks_read{instance=~\"$Instance\"}))*100", - "refId": "A" - } - ], - "title": "Shared Buffer Hits (Postgres)", - "type": "gauge" - }, - { - "datasource": "Prometheus", - "description": "Percentage of max_connections used", - "fieldConfig": { - "defaults": { - "decimals": 0, - "mappings": [ - { - "id": 0, - "op": "=", - "text": "N/A", - "type": 1, - "value": "null" - } - ], - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "semi-dark-green" - }, - { - "color": "semi-dark-yellow", - "value": 0.75 - }, - { - "color": "semi-dark-red", - "value": 0.9 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 3, - "x": 3, - "y": 116 - }, - "id": 9, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "expr": "sum(pg_stat_database_numbackends)/max(pg_settings_max_connections)", - "refId": "A" - } - ], - "title": "Connections used (Postgres)", - "type": "gauge" - }, - { - "datasource": "Prometheus", - "description": "Transaction committed vs rollbacked", - "fieldConfig": { - "defaults": { - "decimals": 2, - "mappings": [ - { - "id": 0, - "op": "=", - "text": "N/A", - "type": 1, - "value": "null" - } - ], - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "semi-dark-red" - }, - { - "color": "#EAB839", - "value": 0.75 - }, - { - "color": "semi-dark-green", - "value": 0.9 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 3, - "x": 6, - "y": 116 - }, - "id": 15, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "10.4.2", - "targets": [ - { - "expr": "sum(pg_stat_database_xact_commit{instance=\"$Instance\"})/(sum(pg_stat_database_xact_commit{instance=\"$Instance\"}) + sum(pg_stat_database_xact_rollback{instance=\"$Instance\"}))", - "refId": "A" - } - ], - "title": "Commit Ratio (Postgres)", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 51, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 6, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 116 - }, - "id": 142, - "options": { - "legend": { - "calcs": [ - "last", - "max" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "pg_stat_user_tables_n_live_tup{datname=\"postgres\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "Live", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "pg_stat_user_tables_n_dead_tup", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "Dead", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Estimated number of rows (Postgres)", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "decimals": 0, - "description": "View: pg_stat_activity", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 123 - }, - "hiddenSeries": false, - "id": 24, - "interval": "$Interval", - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideEmpty": true, - "hideZero": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "links": [ - { - "targetBlank": true, - "title": "PostgreSQL Documentation", - "url": "https://www.postgresql.org/docs/current/monitoring-stats.html" - } - ], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (state) (pg_stat_activity_count{instance=\"$Instance\"})", - "legendFormat": "{{state}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Connections by state (stacked) (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": 0, - "format": "short", - "logBase": 1, - "show": true - }, - { - "decimals": 0, - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "decimals": 0, - "description": "View: pg_stat_activity", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 124 - }, - "hiddenSeries": false, - "id": 121, - "interval": "$Interval", - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideEmpty": true, - "hideZero": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "links": [ - { - "targetBlank": true, - "title": "PostgreSQL Documentation", - "url": "https://www.postgresql.org/docs/current/monitoring-stats.html" - } - ], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (datname) (pg_stat_activity_count{instance=\"$Instance\"})", - "legendFormat": "{{datname}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Connections by database (stacked) (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": 0, - "format": "short", - "logBase": 1, - "show": true - }, - { - "decimals": 0, - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "decimals": 2, - "description": "1 Minute rate of transactions committed or rollback.", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 132 - }, - "hiddenSeries": false, - "id": 122, - "interval": "", - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideEmpty": false, - "hideZero": false, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum ((rate(pg_stat_database_xact_commit[$Interval])))", - "interval": "", - "legendFormat": "committed", - "refId": "A" - }, - { - "expr": "sum ((rate(pg_stat_database_xact_rollback[$Interval])))", - "hide": false, - "interval": "", - "legendFormat": "rollback", - "refId": "B" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Transactions (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Source: pg_stat_database", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 133 - }, - "hiddenSeries": false, - "id": 27, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideZero": false, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum((rate(pg_stat_database_tup_inserted{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "Inserts", - "refId": "A" - }, - { - "expr": "sum((rate(pg_stat_database_tup_updated{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "Updates", - "refId": "B" - }, - { - "expr": "sum((rate(pg_stat_database_tup_deleted{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "Deletes", - "refId": "C" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Tuples inserts/updates/deletes (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "none", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "* blk_read_time: Time spent reading data file blocks by backends in this database, in milliseconds\n* blk_write_time: Time spent writing data file blocks by backends in this database, in milliseconds\n\ntrack_io_timings needs to be activated", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 141 - }, - "hiddenSeries": false, - "id": 26, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum ((rate(pg_stat_database_blk_read_time{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "blk_read_time", - "refId": "A" - }, - { - "expr": "sum ((rate(pg_stat_database_blk_write_time{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "blk_read_time", - "refId": "B" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "I/O Read/Write time (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Source: pg_stat_database\n\n* tup_fetched: rows needed to satisfy queries\n* tup_returned: rows read/scanned", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 142 - }, - "hiddenSeries": false, - "id": 111, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideZero": false, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum((rate(pg_stat_database_tup_fetched{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "Fetched", - "refId": "A" - }, - { - "expr": "sum((rate(pg_stat_database_tup_returned{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "Returned", - "refId": "B" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Tuples fetched/returned (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": false - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Source: pg_locks", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 150 - }, - "hiddenSeries": false, - "id": 123, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [ - { - "title": "PostgreSQL Lock Modes", - "url": "https://www.postgresql.org/docs/12/explicit-locking.html#LOCKING-TABLES" - } - ], - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (mode) (pg_locks_count{instance=\"$Instance\"})", - "legendFormat": "{{mode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Locks by state (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "none", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Should be 0 \n\nSource: pg_stat_database\n\nWith log_lock_waits turned on, deadlocks will be logged to the PostgreSQL Logfiles.", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 151 - }, - "hiddenSeries": false, - "id": 30, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideEmpty": true, - "hideZero": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [ - { - "title": "PostgreSQL Locking", - "url": "https://www.postgresql.org/docs/12/explicit-locking.html" - } - ], - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (datname) ((rate(pg_stat_database_deadlocks{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "{{datname}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Deadlocks by database (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "none", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Should be 0. If temporary files are created, it can indicate insufficient work_mem. With log_temp_files the creation of temporary files are logged to the PostgreSQL Logfiles.", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 156 - }, - "hiddenSeries": false, - "id": 31, - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "hideEmpty": true, - "hideZero": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [ - { - "title": "PostgreSQL Ressources", - "url": "https://www.postgresql.org/docs/current/runtime-config-resource.html" - } - ], - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (datname) ((rate(pg_stat_database_temp_files{instance=\"$Instance\"}[$Interval])))", - "interval": "", - "legendFormat": "{{datname}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Temporary files by database (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "none", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "description": "Lag behind master in seconds.\n\nOnly available on a standby System.", - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 160 - }, - "hiddenSeries": false, - "id": 120, - "interval": "1m", - "legend": { - "alignAsTable": true, - "avg": true, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.4.2", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "max(pg_replication_lag{instance=\"$Instance\"})", - "instant": false, - "intervalFactor": 1, - "legendFormat": "lag ", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Replication lag (Postgres)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "logBase": 1, - "min": "0", - "show": true - }, - { - "format": "short", - "logBase": 1, - "min": "0", - "show": true - } - ], - "yaxis": { - "align": false - } - } - ], - "revision": 1, - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [ - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "definition": "label_values({job=\"postgres-exporter\"}, instance)", - "hide": 0, - "includeAll": false, - "multi": false, - "name": "Instance", - "options": [], - "query": "label_values({job=\"postgres-exporter\"}, instance)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "definition": "label_values(datname)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "Database", - "options": [], - "query": "label_values(datname)", - "refresh": 1, - "regex": "/^(?!template*|postgres).*$/", - "skipUrlSync": false, - "sort": 1, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "auto": true, - "auto_count": 30, - "auto_min": "10s", - "current": { - "selected": false, - "text": "10m", - "value": "10m" - }, - "hide": 0, - "name": "Interval", - "options": [ - { - "selected": false, - "text": "auto", - "value": "$__auto_interval_Interval" - }, - { - "selected": false, - "text": "30sec", - "value": "30sec" - }, - { - "selected": false, - "text": "1m", - "value": "1m" - }, - { - "selected": true, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "6h", - "value": "6h" - }, - { - "selected": false, - "text": "12h", - "value": "12h" - }, - { - "selected": false, - "text": "1d", - "value": "1d" - } - ], - "query": "30sec,1m,10m,30m,1h,6h,12h,1d", - "queryValue": "", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - } - ] - }, - "time": { - "from": "2024-06-26T02:42:06.763Z", - "to": "2024-06-26T04:42:06.771Z" - }, - "timepicker": { - "refresh_intervals": [ - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "browser", - "title": "nwaku-monitoring", - "uid": "yns_4vFVk", - "version": 1, - "weekStart": "" -} \ No newline at end of file diff --git a/apps/liteprotocoltester/monitoring/configuration/datasources.yaml b/apps/liteprotocoltester/monitoring/configuration/datasources.yaml index 9f4f51f86a..2cc211feeb 100644 --- a/apps/liteprotocoltester/monitoring/configuration/datasources.yaml +++ b/apps/liteprotocoltester/monitoring/configuration/datasources.yaml @@ -5,7 +5,7 @@ datasources: type: prometheus access: proxy org_id: 1 - url: http://prometheus:9090 + url: http://prometheus:9099 is_default: true version: 1 - editable: true \ No newline at end of file + editable: true diff --git a/apps/liteprotocoltester/monitoring/configuration/grafana.ini b/apps/liteprotocoltester/monitoring/configuration/grafana.ini index f237726b3e..631fbb73af 100644 --- a/apps/liteprotocoltester/monitoring/configuration/grafana.ini +++ b/apps/liteprotocoltester/monitoring/configuration/grafana.ini @@ -1,9 +1,11 @@ -instance_name = nwaku dashboard +instance_name = liteprotocoltester dashboard ;[dashboards.json] ;enabled = true ;path = /home/git/grafana/grafana-dashboards/dashboards +[server] +http_port = 3033 #################################### Auth ########################## [auth] diff --git a/apps/liteprotocoltester/monitoring/configuration/pg-exporter-queries.yml b/apps/liteprotocoltester/monitoring/configuration/pg-exporter-queries.yml deleted file mode 100644 index bb1d7320a1..0000000000 --- a/apps/liteprotocoltester/monitoring/configuration/pg-exporter-queries.yml +++ /dev/null @@ -1,284 +0,0 @@ -pg_replication: - query: "SELECT CASE WHEN NOT pg_is_in_recovery() THEN 0 ELSE GREATEST (0, EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))) END AS lag" - master: true - metrics: - - lag: - usage: "GAUGE" - description: "Replication lag behind master in seconds" - -pg_postmaster: - query: "SELECT pg_postmaster_start_time as start_time_seconds from pg_postmaster_start_time()" - master: true - metrics: - - start_time_seconds: - usage: "GAUGE" - description: "Time at which postmaster started" - -pg_stat_user_tables: - query: | - SELECT - current_database() datname, - schemaname, - relname, - seq_scan, - seq_tup_read, - idx_scan, - idx_tup_fetch, - n_tup_ins, - n_tup_upd, - n_tup_del, - n_tup_hot_upd, - n_live_tup, - n_dead_tup, - n_mod_since_analyze, - COALESCE(last_vacuum, '1970-01-01Z') as last_vacuum, - COALESCE(last_autovacuum, '1970-01-01Z') as last_autovacuum, - COALESCE(last_analyze, '1970-01-01Z') as last_analyze, - COALESCE(last_autoanalyze, '1970-01-01Z') as last_autoanalyze, - vacuum_count, - autovacuum_count, - analyze_count, - autoanalyze_count - FROM - pg_stat_user_tables - metrics: - - datname: - usage: "LABEL" - description: "Name of current database" - - schemaname: - usage: "LABEL" - description: "Name of the schema that this table is in" - - relname: - usage: "LABEL" - description: "Name of this table" - - seq_scan: - usage: "COUNTER" - description: "Number of sequential scans initiated on this table" - - seq_tup_read: - usage: "COUNTER" - description: "Number of live rows fetched by sequential scans" - - idx_scan: - usage: "COUNTER" - description: "Number of index scans initiated on this table" - - idx_tup_fetch: - usage: "COUNTER" - description: "Number of live rows fetched by index scans" - - n_tup_ins: - usage: "COUNTER" - description: "Number of rows inserted" - - n_tup_upd: - usage: "COUNTER" - description: "Number of rows updated" - - n_tup_del: - usage: "COUNTER" - description: "Number of rows deleted" - - n_tup_hot_upd: - usage: "COUNTER" - description: "Number of rows HOT updated (i.e., with no separate index update required)" - - n_live_tup: - usage: "GAUGE" - description: "Estimated number of live rows" - - n_dead_tup: - usage: "GAUGE" - description: "Estimated number of dead rows" - - n_mod_since_analyze: - usage: "GAUGE" - description: "Estimated number of rows changed since last analyze" - - last_vacuum: - usage: "GAUGE" - description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" - - last_autovacuum: - usage: "GAUGE" - description: "Last time at which this table was vacuumed by the autovacuum daemon" - - last_analyze: - usage: "GAUGE" - description: "Last time at which this table was manually analyzed" - - last_autoanalyze: - usage: "GAUGE" - description: "Last time at which this table was analyzed by the autovacuum daemon" - - vacuum_count: - usage: "COUNTER" - description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" - - autovacuum_count: - usage: "COUNTER" - description: "Number of times this table has been vacuumed by the autovacuum daemon" - - analyze_count: - usage: "COUNTER" - description: "Number of times this table has been manually analyzed" - - autoanalyze_count: - usage: "COUNTER" - description: "Number of times this table has been analyzed by the autovacuum daemon" - -pg_statio_user_tables: - query: "SELECT current_database() datname, schemaname, relname, heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit, toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit FROM pg_statio_user_tables" - metrics: - - datname: - usage: "LABEL" - description: "Name of current database" - - schemaname: - usage: "LABEL" - description: "Name of the schema that this table is in" - - relname: - usage: "LABEL" - description: "Name of this table" - - heap_blks_read: - usage: "COUNTER" - description: "Number of disk blocks read from this table" - - heap_blks_hit: - usage: "COUNTER" - description: "Number of buffer hits in this table" - - idx_blks_read: - usage: "COUNTER" - description: "Number of disk blocks read from all indexes on this table" - - idx_blks_hit: - usage: "COUNTER" - description: "Number of buffer hits in all indexes on this table" - - toast_blks_read: - usage: "COUNTER" - description: "Number of disk blocks read from this table's TOAST table (if any)" - - toast_blks_hit: - usage: "COUNTER" - description: "Number of buffer hits in this table's TOAST table (if any)" - - tidx_blks_read: - usage: "COUNTER" - description: "Number of disk blocks read from this table's TOAST table indexes (if any)" - - tidx_blks_hit: - usage: "COUNTER" - description: "Number of buffer hits in this table's TOAST table indexes (if any)" - -# WARNING: This set of metrics can be very expensive on a busy server as every unique query executed will create an additional time series -pg_stat_statements: - query: "SELECT t2.rolname, t3.datname, queryid, calls, ( total_plan_time + total_exec_time ) / 1000 as total_time_seconds, ( min_plan_time + min_exec_time ) / 1000 as min_time_seconds, ( max_plan_time + max_exec_time ) / 1000 as max_time_seconds, ( mean_plan_time + mean_exec_time ) / 1000 as mean_time_seconds, ( stddev_plan_time + stddev_exec_time ) / 1000 as stddev_time_seconds, rows, shared_blks_hit, shared_blks_read, shared_blks_dirtied, shared_blks_written, local_blks_hit, local_blks_read, local_blks_dirtied, local_blks_written, temp_blks_read, temp_blks_written, blk_read_time / 1000 as blk_read_time_seconds, blk_write_time / 1000 as blk_write_time_seconds FROM pg_stat_statements t1 JOIN pg_roles t2 ON (t1.userid=t2.oid) JOIN pg_database t3 ON (t1.dbid=t3.oid) WHERE t2.rolname != 'rdsadmin' AND queryid IS NOT NULL" - master: true - metrics: - - rolname: - usage: "LABEL" - description: "Name of user" - - datname: - usage: "LABEL" - description: "Name of database" - - queryid: - usage: "LABEL" - description: "Query ID" - - calls: - usage: "COUNTER" - description: "Number of times executed" - - total_time_seconds: - usage: "COUNTER" - description: "Total time spent in the statement, in milliseconds" - - min_time_seconds: - usage: "GAUGE" - description: "Minimum time spent in the statement, in milliseconds" - - max_time_seconds: - usage: "GAUGE" - description: "Maximum time spent in the statement, in milliseconds" - - mean_time_seconds: - usage: "GAUGE" - description: "Mean time spent in the statement, in milliseconds" - - stddev_time_seconds: - usage: "GAUGE" - description: "Population standard deviation of time spent in the statement, in milliseconds" - - rows: - usage: "COUNTER" - description: "Total number of rows retrieved or affected by the statement" - - shared_blks_hit: - usage: "COUNTER" - description: "Total number of shared block cache hits by the statement" - - shared_blks_read: - usage: "COUNTER" - description: "Total number of shared blocks read by the statement" - - shared_blks_dirtied: - usage: "COUNTER" - description: "Total number of shared blocks dirtied by the statement" - - shared_blks_written: - usage: "COUNTER" - description: "Total number of shared blocks written by the statement" - - local_blks_hit: - usage: "COUNTER" - description: "Total number of local block cache hits by the statement" - - local_blks_read: - usage: "COUNTER" - description: "Total number of local blocks read by the statement" - - local_blks_dirtied: - usage: "COUNTER" - description: "Total number of local blocks dirtied by the statement" - - local_blks_written: - usage: "COUNTER" - description: "Total number of local blocks written by the statement" - - temp_blks_read: - usage: "COUNTER" - description: "Total number of temp blocks read by the statement" - - temp_blks_written: - usage: "COUNTER" - description: "Total number of temp blocks written by the statement" - - blk_read_time_seconds: - usage: "COUNTER" - description: "Total time the statement spent reading blocks, in milliseconds (if track_io_timing is enabled, otherwise zero)" - - blk_write_time_seconds: - usage: "COUNTER" - description: "Total time the statement spent writing blocks, in milliseconds (if track_io_timing is enabled, otherwise zero)" - -pg_process_idle: - query: | - WITH - metrics AS ( - SELECT - application_name, - SUM(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change))::bigint)::float AS process_idle_seconds_sum, - COUNT(*) AS process_idle_seconds_count - FROM pg_stat_activity - WHERE state = 'idle' - GROUP BY application_name - ), - buckets AS ( - SELECT - application_name, - le, - SUM( - CASE WHEN EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change)) <= le - THEN 1 - ELSE 0 - END - )::bigint AS bucket - FROM - pg_stat_activity, - UNNEST(ARRAY[1, 2, 5, 15, 30, 60, 90, 120, 300]) AS le - GROUP BY application_name, le - ORDER BY application_name, le - ) - SELECT - application_name, - process_idle_seconds_sum as seconds_sum, - process_idle_seconds_count as seconds_count, - ARRAY_AGG(le) AS seconds, - ARRAY_AGG(bucket) AS seconds_bucket - FROM metrics JOIN buckets USING (application_name) - GROUP BY 1, 2, 3 - metrics: - - application_name: - usage: "LABEL" - description: "Application Name" - - seconds: - usage: "HISTOGRAM" - description: "Idle time of server processes" - -pg_tb_stats: - query: | - select pubsubtopic, count(*) AS messages FROM (SELECT id, array_agg(pubsubtopic ORDER BY pubsubtopic) AS pubsubtopic FROM messages GROUP BY id) sub GROUP BY pubsubtopic ORDER BY pubsubtopic; - metrics: - - pubsubtopic: - usage: "LABEL" - description: "pubsubtopic" - - messages: - usage: "GAUGE" - description: "Number of messages for the given pubsub topic" - -pg_tb_messages: - query: | - SELECT - COUNT(ID) - FROM messages - metrics: - - count: - usage: "GAUGE" - description: "Row count in `messages` table" diff --git a/apps/liteprotocoltester/monitoring/configuration/postgres-exporter.yml b/apps/liteprotocoltester/monitoring/configuration/postgres-exporter.yml deleted file mode 100644 index a8380dd72e..0000000000 --- a/apps/liteprotocoltester/monitoring/configuration/postgres-exporter.yml +++ /dev/null @@ -1,9 +0,0 @@ -auth_modules: - mypostgres: - type: userpass - userpass: - username: postgres - password: ${POSTGRES_PASSWORD} - options: - # options become key=value parameters of the DSN - sslmode: disable diff --git a/apps/liteprotocoltester/monitoring/prometheus-config.yml b/apps/liteprotocoltester/monitoring/prometheus-config.yml index 650798fdcf..7ea2d1747a 100644 --- a/apps/liteprotocoltester/monitoring/prometheus-config.yml +++ b/apps/liteprotocoltester/monitoring/prometheus-config.yml @@ -5,6 +5,14 @@ global: monitor: "Monitoring" scrape_configs: - - job_name: "nwaku" + - job_name: "liteprotocoltester" static_configs: - - targets: ["nwaku:8003"] + - targets: ["lightpush-service:8003", + "filter-service:8003", + "liteprotocoltester-publishernode-1:8003", + "liteprotocoltester-publishernode-2:8003", + "liteprotocoltester-publishernode-3:8003", + "liteprotocoltester-publishernode-4:8003", + "liteprotocoltester-publishernode-5:8003", + "liteprotocoltester-publishernode-6:8003", + "receivernode:8003"] diff --git a/apps/liteprotocoltester/nim.cfg b/apps/liteprotocoltester/nim.cfg new file mode 100644 index 0000000000..2231f2ebed --- /dev/null +++ b/apps/liteprotocoltester/nim.cfg @@ -0,0 +1,4 @@ +-d:chronicles_line_numbers +-d:chronicles_runtime_filtering:on +-d:discv5_protocol_id:d5waku +path = "../.." diff --git a/apps/liteprotocoltester/run_service_node.sh b/apps/liteprotocoltester/run_service_node.sh old mode 100644 new mode 100755 index ba50782c29..1d36292c11 --- a/apps/liteprotocoltester/run_service_node.sh +++ b/apps/liteprotocoltester/run_service_node.sh @@ -5,6 +5,39 @@ IP=$(ip a | grep "inet " | grep -Fv 127.0.0.1 | sed 's/.*inet \([^/]*\).*/\1/') echo "Service node IP: ${IP}" +if [ -n "${PUBSUB}" ]; then + PUBSUB=--pubsub-topic="${PUBSUB}" +else + PUBSUB=--pubsub-topic="/waku/2/rs/66/0" +fi + +if [ -n "${CLUSTER_ID}" ]; then + CLUSTER_ID=--cluster-id="${CLUSTER_ID}" +fi + +echo "STANDALONE: ${STANDALONE}" + +if [ -z "${STANDALONE}" ]; then + + RETRIES=${RETRIES:=20} + + while [ -z "${BOOTSTRAP_ENR}" ] && [ ${RETRIES} -ge 0 ]; do + BOOTSTRAP_ENR=$(wget -qO- http://bootstrap:8645/debug/v1/info --header='Content-Type:application/json' 2> /dev/null | sed 's/.*"enrUri":"\([^"]*\)".*/\1/'); + echo "Bootstrap node not ready, retrying (retries left: ${RETRIES})" + sleep 3 + RETRIES=$(( $RETRIES - 1 )) + done + + if [ -z "${BOOTSTRAP_ENR}" ]; then + echo "Could not get BOOTSTRAP_ENR and none provided. Failing" + exit 1 + fi + + echo "Using bootstrap node: ${BOOTSTRAP_ENR}" + +fi + + exec /usr/bin/wakunode\ --relay=true\ --filter=true\ @@ -20,10 +53,11 @@ exec /usr/bin/wakunode\ --dns-discovery=true\ --discv5-discovery=true\ --discv5-enr-auto-update=True\ - --log-level=DEBUG\ + --discv5-bootstrap-node=${BOOTSTRAP_ENR}\ + --log-level=INFO\ --metrics-server=True\ + --metrics-server-port=8003\ --metrics-server-address=0.0.0.0\ - --nodekey=e3f5e64568b3a612dee609f6e7c0203c501dab6131662922bdcbcabd474281d5\ --nat=extip:${IP}\ - --pubsub-topic=/waku/2/rs/0/0\ - --cluster-id=0 + ${PUBSUB}\ + ${CLUSTER_ID} diff --git a/apps/liteprotocoltester/run_tester_node.sh b/apps/liteprotocoltester/run_tester_node.sh old mode 100644 new mode 100755 index 6c633f5dde..146eb1b25d --- a/apps/liteprotocoltester/run_tester_node.sh +++ b/apps/liteprotocoltester/run_tester_node.sh @@ -1,76 +1,133 @@ #!/bin/sh +#set -x + if test -f .env; then echo "Using .env file" . $(pwd)/.env fi -IP=$(ip a | grep "inet " | grep -Fv 127.0.0.1 | sed 's/.*inet \([^/]*\).*/\1/') echo "I am a lite-protocol-tester node" -# Get an unique node index based on the container's IP -FOURTH_OCTET=${IP##*.} -THIRD_OCTET="${IP%.*}"; THIRD_OCTET="${THIRD_OCTET##*.}" -NODE_INDEX=$((FOURTH_OCTET + 256 * THIRD_OCTET)) +BINARY_PATH=$1 + +if [ ! -x "${BINARY_PATH}" ]; then + echo "Invalid binary path '${BINARY_PATH}'. Failing" + exit 1 +fi + +if [ "${2}" = "--help" ]; then + echo "You might want to check nwaku/apps/liteprotocoltester/README.md" + exec "${BINARY_PATH}" --help + exit 0 +fi + +FUNCTION=$2 +if [ "${FUNCTION}" = "SENDER" ]; then + FUNCTION=--test-func=SENDER + SERVICENAME=lightpush-service +fi + +if [ "${FUNCTION}" = "RECEIVER" ]; then + FUNCTION=--test-func=RECEIVER + SERVICENAME=filter-service +fi + +SERIVCE_NODE_ADDR=$3 +if [ -z "${SERIVCE_NODE_ADDR}" ]; then + echo "Service node peer_id provided. Failing" + exit 1 +fi + +DO_DETECT_SERVICENODE=0 + +if [ "${SERIVCE_NODE_ADDR}" = "servicenode" ]; then + DO_DETECT_SERVICENODE=1 + SERIVCE_NODE_ADDR="" + SERVICENAME=servicenode +fi + +if [ "${SERIVCE_NODE_ADDR}" = "waku-sim" ]; then + DO_DETECT_SERVICENODE=1 + SERIVCE_NODE_ADDR="" + MY_EXT_IP=$(ip a | grep "inet " | grep -Fv 127.0.0.1 | sed 's/.*inet \([^/]*\).*/\1/') +else + MY_EXT_IP=$(wget -qO- --no-check-certificate https://api4.ipify.org) +fi + -echo "NODE_INDEX $NODE_INDEX" +if [ $DO_DETECT_SERVICENODE -eq 1 ]; then + RETRIES=${RETRIES:=20} -RETRIES=${RETRIES:=10} + while [ -z "${SERIVCE_NODE_ADDR}" ] && [ ${RETRIES} -ge 0 ]; do + SERVICE_DEBUG_INFO=$(wget -qO- http://${SERVICENAME}:8645/debug/v1/info --header='Content-Type:application/json' 2> /dev/null); + echo "SERVICE_DEBUG_INFO: ${SERVICE_DEBUG_INFO}" -while [ -z "${SERIVCE_NODE_ADDR}" ] && [ ${RETRIES} -ge 0 ]; do - SERIVCE_NODE_ADDR=$(wget -qO- http://servicenode:8645/debug/v1/info --header='Content-Type:application/json' 2> /dev/null | sed 's/.*"listenAddresses":\["\([^"]*\)".*/\1/'); - echo "Service node not ready, retrying (retries left: ${RETRIES})" - sleep 1 - RETRIES=$(( $RETRIES - 1 )) -done + SERIVCE_NODE_ADDR=$(wget -qO- http://${SERVICENAME}:8645/debug/v1/info --header='Content-Type:application/json' 2> /dev/null | sed 's/.*"listenAddresses":\["\([^"]*\)".*/\1/'); + echo "Service node not ready, retrying (retries left: ${RETRIES})" + sleep 3 + RETRIES=$(( $RETRIES - 1 )) + done + +fi if [ -z "${SERIVCE_NODE_ADDR}" ]; then echo "Could not get SERIVCE_NODE_ADDR and none provided. Failing" exit 1 fi - if [ -n "${PUBSUB}" ]; then PUBSUB=--pubsub-topic="${PUBSUB}" +else + PUBSUB=--pubsub-topic="/waku/2/rs/66/0" fi if [ -n "${CONTENT_TOPIC}" ]; then CONTENT_TOPIC=--content-topic="${CONTENT_TOPIC}" fi -FUNCTION=$1 +if [ -n "${CLUSTER_ID}" ]; then + CLUSTER_ID=--cluster-id="${CLUSTER_ID}" +fi -echo "Tester node: ${FUNCTION}" +if [ -n "${START_PUBLISHING_AFTER}" ]; then + START_PUBLISHING_AFTER=--start-publishing-after="${START_PUBLISHING_AFTER}" +fi -REST_PORT=--rest-port=8647 +if [ -n "${MIN_MESSAGE_SIZE}" ]; then + MIN_MESSAGE_SIZE=--min-test-msg-size="${MIN_MESSAGE_SIZE}" +fi -if [ "${FUNCTION}" = "SENDER" ]; then - FUNCTION=--test-func=SENDER - REST_PORT=--rest-port=8646 +if [ -n "${MAX_MESSAGE_SIZE}" ]; then + MAX_MESSAGE_SIZE=--max-test-msg-size="${MAX_MESSAGE_SIZE}" fi -if [ "${FUNCTION}" = "RECEIVER" ]; then - FUNCTION=--test-func=RECEIVER - REST_PORT=--rest-port=8647 + +if [ -n "${NUM_MESSAGES}" ]; then + NUM_MESSAGES=--num-messages="${NUM_MESSAGES}" fi -if [ -z "${FUNCTION}" ]; then - FUNCTION=--test-func=RECEIVER +if [ -n "${DELAY_MESSAGES}" ]; then + DELAY_MESSAGES=--delay-messages="${DELAY_MESSAGES}" fi +echo "Running binary: ${BINARY_PATH}" +echo "Tester node: ${FUNCTION}" echo "Using service node: ${SERIVCE_NODE_ADDR}" -exec /usr/bin/liteprotocoltester\ - --log-level=DEBUG\ +echo "My external IP: ${MY_EXT_IP}" + +exec "${BINARY_PATH}"\ + --log-level=INFO\ --service-node="${SERIVCE_NODE_ADDR}"\ - --pubsub-topic=/waku/2/rs/0/0\ - --cluster-id=0\ - --num-messages=${NUM_MESSAGES}\ - --delay-messages=${DELAY_MESSAGES}\ - --nat=extip:${IP}\ - ${FUNCTION}\ + --nat=extip:${MY_EXT_IP}\ + ${DELAY_MESSAGES}\ + ${NUM_MESSAGES}\ ${PUBSUB}\ ${CONTENT_TOPIC}\ - ${REST_PORT} - + ${CLUSTER_ID}\ + ${FUNCTION}\ + ${START_PUBLISHING_AFTER}\ + ${MIN_MESSAGE_SIZE}\ + ${MAX_MESSAGE_SIZE} # --config-file=config.toml\ diff --git a/apps/liteprotocoltester/statistics.nim b/apps/liteprotocoltester/statistics.nim index 13e749193c..db4a8a81a1 100644 --- a/apps/liteprotocoltester/statistics.nim +++ b/apps/liteprotocoltester/statistics.nim @@ -4,27 +4,37 @@ import std/[sets, tables, strutils, sequtils, options, strformat], chronos/timer as chtimer, chronicles, - results + chronos, + results, + libp2p/peerid -import ./tester_message +import ./tester_message, ./lpt_metrics type + ArrivalInfo = object + arrivedAt: Moment + prevArrivedAt: Moment + prevIndex: uint32 + + MessageInfo = tuple[msg: ProtocolTesterMessage, info: ArrivalInfo] + DupStat = tuple[hash: string, dupCount: int, size: uint64] + StatHelper = object prevIndex: uint32 prevArrivedAt: Moment lostIndices: HashSet[uint32] seenIndices: HashSet[uint32] maxIndex: uint32 + duplicates: OrderedTable[uint32, DupStat] Statistics* = object + received: Table[uint32, MessageInfo] + firstReceivedIdx*: uint32 allMessageCount*: uint32 receivedMessages*: uint32 misorderCount*: uint32 lateCount*: uint32 duplicateCount*: uint32 - minLatency*: Duration - maxLatency*: Duration - cummulativeLatency: Duration helper: StatHelper PerPeerStatistics* = Table[string, Statistics] @@ -42,24 +52,39 @@ proc init*(T: type Statistics, expectedMessageCount: int = 1000): T = result.helper.prevIndex = 0 result.helper.maxIndex = 0 result.helper.seenIndices.init(expectedMessageCount) - result.minLatency = nanos(0) - result.maxLatency = nanos(0) - result.cummulativeLatency = nanos(0) + result.received = initTable[uint32, MessageInfo](expectedMessageCount) return result -proc addMessage*(self: var Statistics, msg: ProtocolTesterMessage) = +proc addMessage*( + self: var Statistics, sender: string, msg: ProtocolTesterMessage, msgHash: string +) = if self.allMessageCount == 0: self.allMessageCount = msg.count + self.firstReceivedIdx = msg.index elif self.allMessageCount != msg.count: - warn "Message count mismatch at message", + error "Message count mismatch at message", index = msg.index, expected = self.allMessageCount, got = msg.count - if not self.helper.seenIndices.contains(msg.index): - self.helper.seenIndices.incl(msg.index) - else: + let currentArrived: MessageInfo = ( + msg: msg, + info: ArrivalInfo( + arrivedAt: Moment.now(), + prevArrivedAt: self.helper.prevArrivedAt, + prevIndex: self.helper.prevIndex, + ), + ) + lpt_receiver_received_bytes.inc(labelValues = [sender], amount = msg.size.int64) + if self.received.hasKeyOrPut(msg.index, currentArrived): inc(self.duplicateCount) - warn "Duplicate message", index = msg.index - ## just do not count into stats + self.helper.duplicates.mgetOrPut(msg.index, (msgHash, 0, msg.size)).dupCount.inc() + warn "Duplicate message", + index = msg.index, + hash = msgHash, + times_duplicated = self.helper.duplicates[msg.index].dupCount + lpt_receiver_duplicate_messages_count.inc(labelValues = [sender]) + lpt_receiver_distinct_duplicate_messages_count.set( + labelValues = [sender], value = self.helper.duplicates.len() + ) return ## detect misorder arrival and possible lost messages @@ -67,70 +92,124 @@ proc addMessage*(self: var Statistics, msg: ProtocolTesterMessage) = inc(self.misorderCount) warn "Misordered message arrival", index = msg.index, expected = self.helper.prevIndex + 1 - - ## collect possible lost message indicies - for idx in self.helper.prevIndex + 1 ..< msg.index: - self.helper.lostIndices.incl(idx) elif self.helper.prevIndex > msg.index: inc(self.lateCount) warn "Late message arrival", index = msg.index, expected = self.helper.prevIndex + 1 - else: - ## may remove late arrival - self.helper.lostIndices.excl(msg.index) - - ## calculate latency - let currentArrivedAt = Moment.now() - - let delaySincePrevArrived: Duration = currentArrivedAt - self.helper.prevArrivedAt - - let expectedDelay: Duration = nanos(msg.sincePrev) - - var latency: Duration - - # if we have any latency... - if expectedDelay > delaySincePrevArrived: - latency = delaySincePrevArrived - expectedDelay - if self.minLatency.isZero or (latency < self.minLatency and latency > nanos(0)): - self.minLatency = latency - if latency > self.maxLatency: - self.maxLatency = latency - self.cummulativeLatency += latency - else: - warn "Negative latency detected", - index = msg.index, expected = expectedDelay, actual = delaySincePrevArrived self.helper.maxIndex = max(self.helper.maxIndex, msg.index) self.helper.prevIndex = msg.index - self.helper.prevArrivedAt = currentArrivedAt + self.helper.prevArrivedAt = currentArrived.info.arrivedAt inc(self.receivedMessages) + lpt_receiver_received_messages_count.inc(labelValues = [sender]) + lpt_receiver_missing_messages_count.set( + labelValues = [sender], value = (self.helper.maxIndex - self.receivedMessages).int64 + ) proc addMessage*( - self: var PerPeerStatistics, peerId: string, msg: ProtocolTesterMessage + self: var PerPeerStatistics, + peerId: string, + msg: ProtocolTesterMessage, + msgHash: string, ) = if not self.contains(peerId): self[peerId] = Statistics.init() + let shortSenderId = block: + let senderPeer = PeerId.init(msg.sender) + if senderPeer.isErr(): + msg.sender + else: + senderPeer.get().shortLog() + discard catch: - self[peerId].addMessage(msg) + self[peerId].addMessage(shortSenderId, msg, msgHash) + + lpt_receiver_sender_peer_count.set(value = self.len) proc lossCount*(self: Statistics): uint32 = self.helper.maxIndex - self.receivedMessages -proc averageLatency*(self: Statistics): Duration = - if self.receivedMessages == 0: - return nanos(0) - return self.cummulativeLatency div self.receivedMessages +proc calcLatency*(self: Statistics): tuple[min, max, avg: Duration] = + var + minLatency = nanos(0) + maxLatency = nanos(0) + avgLatency = nanos(0) + + if self.receivedMessages > 2: + try: + var prevArrivedAt = self.received[self.firstReceivedIdx].info.arrivedAt + + for idx, (msg, arrival) in self.received.pairs: + if idx <= 1: + continue + let expectedDelay = nanos(msg.sincePrev) + + ## latency will be 0 if arrived in shorter time than expected + var latency = arrival.arrivedAt - arrival.prevArrivedAt - expectedDelay + + ## will not measure zero latency, it is unlikely to happen but in case happens could + ## ditort the min latency calulculation as we want to calculate the feasible minimum. + if latency > nanos(0): + if minLatency == nanos(0): + minLatency = latency + else: + minLatency = min(minLatency, latency) + + maxLatency = max(maxLatency, latency) + avgLatency += latency + + avgLatency = avgLatency div (self.receivedMessages - 1) + except KeyError: + error "Error while calculating latency: " & getCurrentExceptionMsg() + + return (minLatency, maxLatency, avgLatency) + +proc missingIndices*(self: Statistics): seq[uint32] = + var missing: seq[uint32] = @[] + for idx in 1 .. self.helper.maxIndex: + if not self.received.hasKey(idx): + missing.add(idx) + return missing + +proc distinctDupCount(self: Statistics): int {.inline.} = + return self.helper.duplicates.len() + +proc allDuplicates(self: Statistics): int {.inline.} = + var total = 0 + for _, (_, dupCount, _) in self.helper.duplicates.pairs: + total += dupCount + return total + +proc dupMsgs(self: Statistics): string = + var dupMsgs: string = "" + for idx, (hash, dupCount, size) in self.helper.duplicates.pairs: + dupMsgs.add( + " index: " & $idx & " | hash: " & hash & " | count: " & $dupCount & " | size: " & + $size & "\n" + ) + return dupMsgs proc echoStat*(self: Statistics) = + let (minL, maxL, avgL) = self.calcLatency() + let printable = catch: """*------------------------------------------------------------------------------------------* -| Expected | Received | Target | Loss | Misorder | Late | Duplicate | -|{self.helper.maxIndex:>11} |{self.receivedMessages:>11} |{self.allMessageCount:>11} |{self.lossCount():>11} |{self.misorderCount:>11} |{self.lateCount:>11} |{self.duplicateCount:>11} | +| Expected | Received | Target | Loss | Misorder | Late | | +|{self.helper.maxIndex:>11} |{self.receivedMessages:>11} |{self.allMessageCount:>11} |{self.lossCount():>11} |{self.misorderCount:>11} |{self.lateCount:>11} | | *------------------------------------------------------------------------------------------* | Latency stat: | -| avg latency: {$self.averageLatency():<73}| -| min latency: {$self.maxLatency:<73}| -| max latency: {$self.minLatency:<73}| +| min latency: {$minL:<73}| +| avg latency: {$avgL:<73}| +| max latency: {$maxL:<73}| +*------------------------------------------------------------------------------------------* +| Duplicate stat: | +| distinct duplicate messages: {$self.distinctDupCount():<57}| +| sum duplicates : {$self.allDuplicates():<57}| + Duplicated messages: + {self.dupMsgs()} +*------------------------------------------------------------------------------------------* +| Lost indices: | +| {self.missingIndices()} | *------------------------------------------------------------------------------------------*""".fmt() if printable.isErr(): @@ -139,6 +218,8 @@ proc echoStat*(self: Statistics) = echo printable.get() proc jsonStat*(self: Statistics): string = + let minL, maxL, avgL = self.calcLatency() + let json = catch: """{{"expected":{self.helper.maxIndex}, "received": {self.receivedMessages}, @@ -148,10 +229,11 @@ proc jsonStat*(self: Statistics): string = "late": {self.lateCount}, "duplicate": {self.duplicateCount}, "latency": - {{"avg": "{self.averageLatency()}", - "min": "{self.minLatency}", - "max": "{self.maxLatency}" - }} + {{"avg": "{avgL}", + "min": "{minL}", + "max": "{maxL}" + }}, + "lostIndices": {self.missingIndices()} }}""".fmt() if json.isErr: return "{\"result:\": \"" & json.error.msg & "\"}" @@ -189,14 +271,25 @@ proc jsonStats*(self: PerPeerStatistics): string = "{\"result:\": \"Error while generating json stats: " & getCurrentExceptionMsg() & "\"}" -proc checkIfAllMessagesReceived*(self: PerPeerStatistics): bool = +proc checkIfAllMessagesReceived*(self: PerPeerStatistics): Future[bool] {.async.} = # if there are no peers have sent messages, assume we just have started. if self.len == 0: return false for stat in self.values: if (stat.allMessageCount == 0 and stat.receivedMessages == 0) or - stat.receivedMessages < stat.allMessageCount: + stat.helper.maxIndex < stat.allMessageCount: return false + ## Ok, we see last message arrived from all peers, + ## lets check if all messages are received + ## and if not let's wait another 20 secs to give chance the system will send them. + var shallWait = false + for stat in self.values: + if stat.receivedMessages < stat.allMessageCount: + shallWait = true + + if shallWait: + await sleepAsync(chtimer.seconds(20)) + return true diff --git a/apps/liteprotocoltester/tester_config.nim b/apps/liteprotocoltester/tester_config.nim index 8055a22955..54221e7950 100644 --- a/apps/liteprotocoltester/tester_config.nim +++ b/apps/liteprotocoltester/tester_config.nim @@ -20,14 +20,16 @@ import common/confutils/envvar/std/net as confEnvvarNet, common/logging, factory/external_config, - waku/waku_core, + waku_core, ] export confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet const - LitePubsubTopic* = PubsubTopic("/waku/2/rs/0/0") + LitePubsubTopic* = PubsubTopic("/waku/2/rs/66/0") LiteContentTopic* = ContentTopic("/tester/1/light-pubsub-example/proto") + DefaultMinTestMessageSizeStr* = "1KiB" + DefaultMaxTestMessageSizeStr* = "150KiB" type TesterFunctionality* = enum SENDER # pumps messages to the network @@ -76,6 +78,12 @@ type LiteProtocolTesterConf* = object desc: "Number of messages to send.", defaultValue: 120, name: "num-messages" .}: uint32 + startPublishingAfter* {. + desc: "Wait number of seconds before start publishing messages.", + defaultValue: 5, + name: "start-publishing-after" + .}: uint32 + delayMessages* {. desc: "Delay between messages in milliseconds.", defaultValue: 1000, @@ -90,7 +98,7 @@ type LiteProtocolTesterConf* = object ## TODO: extend lite protocol tester configuration based on testing needs # shards* {. - # desc: "Shards index to subscribe to [0..MAX_SHARDS-1]. Argument may be repeated.", + # desc: "Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.", # defaultValue: @[], # name: "shard" # .}: seq[uint16] @@ -105,8 +113,21 @@ type LiteProtocolTesterConf* = object "Cluster id that the node is running in. Node in a different cluster id is disconnected.", defaultValue: 0, name: "cluster-id" - .}: uint32 + .}: uint16 + minTestMessageSize* {. + desc: + "Minimum message size. Accepted units: KiB, KB, and B. e.g. 1024KiB; 1500 B; etc.", + defaultValue: DefaultMinTestMessageSizeStr, + name: "min-test-msg-size" + .}: string + + maxTestMessageSize* {. + desc: + "Maximum message size. Accepted units: KiB, KB, and B. e.g. 1024KiB; 1500 B; etc.", + defaultValue: DefaultMaxTestMessageSizeStr, + name: "max-test-msg-size" + .}: string ## Tester REST service configuration restAddress* {. desc: "Listening address of the REST HTTP server.", @@ -138,8 +159,7 @@ proc load*(T: type LiteProtocolTesterConf, version = ""): ConfResult[T] = secondarySources = proc( conf: LiteProtocolTesterConf, sources: auto ) {.gcsafe, raises: [ConfigurationError].} = - sources.addConfigFile(Envvar, InputFile("liteprotocoltester")) - , + sources.addConfigFile(Envvar, InputFile("liteprotocoltester")), ) ok(conf) except CatchableError: diff --git a/apps/liteprotocoltester/tester_message.nim b/apps/liteprotocoltester/tester_message.nim index a34cb4b75b..e91a2a3702 100644 --- a/apps/liteprotocoltester/tester_message.nim +++ b/apps/liteprotocoltester/tester_message.nim @@ -15,6 +15,7 @@ type ProtocolTesterMessage* = object startedAt*: int64 sinceStart*: int64 sincePrev*: int64 + size*: uint64 proc writeValue*( writer: var JsonWriter[RestJson], value: ProtocolTesterMessage @@ -26,6 +27,7 @@ proc writeValue*( writer.writeField("startedAt", value.startedAt) writer.writeField("sinceStart", value.sinceStart) writer.writeField("sincePrev", value.sincePrev) + writer.writeField("size", value.size) writer.endRecord() proc readValue*( @@ -38,6 +40,7 @@ proc readValue*( startedAt: Option[int64] sinceStart: Option[int64] sincePrev: Option[int64] + size: Option[uint64] for fieldName in readObjectFields(reader): case fieldName @@ -77,6 +80,12 @@ proc readValue*( "Multiple `sincePrev` fields found", "ProtocolTesterMessage" ) sincePrev = some(reader.readValue(int64)) + of "size": + if size.isSome(): + reader.raiseUnexpectedField( + "Multiple `size` fields found", "ProtocolTesterMessage" + ) + size = some(reader.readValue(uint64)) else: unrecognizedFieldWarning() @@ -98,6 +107,9 @@ proc readValue*( if sincePrev.isNone(): reader.raiseUnexpectedValue("Field `sincePrev` is missing") + if size.isNone(): + reader.raiseUnexpectedValue("Field `size` is missing") + value = ProtocolTesterMessage( sender: sender.get(), index: index.get(), @@ -105,4 +117,5 @@ proc readValue*( startedAt: startedAt.get(), sinceStart: sinceStart.get(), sincePrev: sincePrev.get(), + size: size.get(), ) diff --git a/apps/networkmonitor/networkmonitor.nim b/apps/networkmonitor/networkmonitor.nim index c8c8986255..09a64c8d54 100644 --- a/apps/networkmonitor/networkmonitor.nim +++ b/apps/networkmonitor/networkmonitor.nim @@ -318,7 +318,7 @@ proc crawlNetwork( let discoveredNodes = await wakuDiscv5.findRandomPeers() # nodes are nested into bucket, flat it - #let flatNodes = wakuDiscv5.protocol.routingTable.buckets.mapIt(it.nodes).flatten() + let flatNodes = wakuDiscv5.protocol.routingTable.buckets.mapIt(it.nodes).flatten() # populate metrics related to capabilities as advertised by the ENR (see waku field) setDiscoveredPeersCapabilities(discoveredNodes) @@ -441,10 +441,12 @@ proc initAndStartApp( ipAddr = some(extIp), tcpPort = some(nodeTcpPort), udpPort = some(nodeUdpPort) ) builder.withWakuCapabilities(flags) - let addShardedTopics = builder.withShardedTopics(conf.pubsubTopics) - if addShardedTopics.isErr(): - error "failed to add sharded topics to ENR", error = addShardedTopics.error - return err($addShardedTopics.error) + + builder.withWakuRelaySharding( + RelayShards(clusterId: conf.clusterId, shardIds: conf.shards) + ).isOkOr: + error "failed to add sharded topics to ENR", error = error + return err("failed to add sharded topics to ENR: " & $error) let recordRes = builder.build() let record = @@ -561,11 +563,14 @@ when isMainModule: let twnClusterConf = ClusterConf.TheWakuNetworkConf() conf.bootstrapNodes = twnClusterConf.discv5BootstrapNodes - conf.pubsubTopics = twnClusterConf.pubsubTopics conf.rlnRelayDynamic = twnClusterConf.rlnRelayDynamic conf.rlnRelayEthContractAddress = twnClusterConf.rlnRelayEthContractAddress conf.rlnEpochSizeSec = twnClusterConf.rlnEpochSizeSec conf.rlnRelayUserMessageLimit = twnClusterConf.rlnRelayUserMessageLimit + conf.numShardsInNetwork = twnClusterConf.numShardsInNetwork + + if conf.shards.len == 0: + conf.shards = toSeq(uint16(0) .. uint16(twnClusterConf.numShardsInNetwork - 1)) if conf.logLevel != LogLevel.NONE: setLogLevel(conf.logLevel) @@ -631,9 +636,11 @@ when isMainModule: error "failed to mount waku metadata protocol: ", err = error quit 1 - for pubsubTopic in conf.pubsubTopics: - # Subscribe the node to the default pubsubtopic, to count messages - subscribeAndHandleMessages(node, pubsubTopic, msgPerContentTopic) + for shard in conf.shards: + # Subscribe the node to the shards, to count messages + subscribeAndHandleMessages( + node, $RelayShard(shardId: shard, clusterId: conf.clusterId), msgPerContentTopic + ) # spawn the routine that crawls the network # TODO: split into 3 routines (discovery, connections, ip2location) diff --git a/apps/networkmonitor/networkmonitor_config.nim b/apps/networkmonitor/networkmonitor_config.nim index 22b74b5865..291144c5ba 100644 --- a/apps/networkmonitor/networkmonitor_config.nim +++ b/apps/networkmonitor/networkmonitor_config.nim @@ -38,10 +38,15 @@ type NetworkMonitorConf* = object name: "dns-discovery-url" .}: string - pubsubTopics* {. - desc: "Default pubsub topic to subscribe to. Argument may be repeated.", - name: "pubsub-topic" - .}: seq[string] + shards* {. + desc: + "Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.", + name: "shard" + .}: seq[uint16] + + numShardsInNetwork* {. + desc: "Number of shards in the network", name: "num-shards-in-network" + .}: uint32 refreshInterval* {. desc: "How often new peers are discovered and connected to (in seconds)", @@ -55,7 +60,7 @@ type NetworkMonitorConf* = object "Cluster id that the node is running in. Node in a different cluster id is disconnected.", defaultValue: 1, name: "cluster-id" - .}: uint32 + .}: uint16 rlnRelay* {. desc: "Enable spam protection through rln-relay: true|false", diff --git a/apps/wakucanary/wakucanary.nim b/apps/wakucanary/wakucanary.nim index a67534f44d..9d7f5450e1 100644 --- a/apps/wakucanary/wakucanary.nim +++ b/apps/wakucanary/wakucanary.nim @@ -81,7 +81,8 @@ type WakuCanaryConf* = object .}: bool shards* {. - desc: "Shards index to subscribe to [0..MAX_SHARDS-1]. Argument may be repeated.", + desc: + "Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.", defaultValue: @[], name: "shard", abbr: "s" diff --git a/apps/wakunode2/wakunode2.nim b/apps/wakunode2/wakunode2.nim index afa84f6537..70703f8ecd 100644 --- a/apps/wakunode2/wakunode2.nim +++ b/apps/wakunode2/wakunode2.nim @@ -58,13 +58,15 @@ when isMainModule: nodeHealthMonitor = WakuNodeHealthMonitor() nodeHealthMonitor.setOverallHealth(HealthStatus.INITIALIZING) + var confCopy = conf + let restServer = rest_server_builder.startRestServerEsentials( - nodeHealthMonitor, conf + nodeHealthMonitor, confCopy ).valueOr: error "Starting esential REST server failed.", error = $error quit(QuitFailure) - var waku = Waku.init(conf).valueOr: + var waku = Waku.init(confCopy).valueOr: error "Waku initialization failed", error = error quit(QuitFailure) @@ -77,12 +79,12 @@ when isMainModule: quit(QuitFailure) rest_server_builder.startRestServerProtocolSupport( - restServer, waku.node, waku.wakuDiscv5, conf + restServer, waku.node, waku.wakuDiscv5, confCopy ).isOkOr: error "Starting protocols support REST server failed.", error = $error quit(QuitFailure) - waku.metricsServer = waku_metrics.startMetricsServerAndLogging(conf).valueOr: + waku.metricsServer = waku_metrics.startMetricsServerAndLogging(confCopy).valueOr: error "Starting monitoring and external interfaces failed", error = error quit(QuitFailure) diff --git a/docs/operators/how-to/run.md b/docs/operators/how-to/run.md index b85efe1bb6..bc90394792 100644 --- a/docs/operators/how-to/run.md +++ b/docs/operators/how-to/run.md @@ -18,7 +18,7 @@ By default a nwaku node will: See [this tutorial](./configure-key.md) if you want to generate and configure a persistent private key. - listen for incoming libp2p connections on the default TCP port (`60000`) - enable `relay` protocol -- subscribe to the default pubsub topic, namely `/waku/2/rs/0/0` +- subscribe to the default clusterId (0) and shard (0) - enable `store` protocol, but only as a client. This implies that the nwaku node will not persist any historical messages itself, but can query `store` service peers who do so. diff --git a/examples/cbindings/waku_example.c b/examples/cbindings/waku_example.c index d1987973fd..39ee89f27f 100644 --- a/examples/cbindings/waku_example.c +++ b/examples/cbindings/waku_example.c @@ -97,7 +97,7 @@ void event_handler(int callerRet, const char* msg, size_t len, void* userData) { exit(1); } else if (callerRet == RET_OK) { - printf("Receiving message %s\n", msg); + printf("Receiving event: %s\n", msg); } } @@ -326,6 +326,10 @@ int main(int argc, char** argv) { event_handler, userData) ); + WAKU_CALL( waku_get_peerids_from_peerstore(ctx, + event_handler, + userData) ); + show_main_menu(); while(1) { handle_user_input(); diff --git a/examples/golang/waku.go b/examples/golang/waku.go index 3cc8756152..3ce66b1867 100644 --- a/examples/golang/waku.go +++ b/examples/golang/waku.go @@ -1,7 +1,8 @@ package main /* - #cgo LDFLAGS: -L../../build/ -lwaku -Wl,--allow-multiple-definition + #cgo LDFLAGS: -L../../build/ -lwaku -lnegentropy -Wl,--allow-multiple-definition + #cgo LDFLAGS: -L../../ -Wl,-rpath,../../ #include "../../library/libwaku.h" #include @@ -171,6 +172,10 @@ package main WAKU_CALL (waku_listen_addresses(ctx, (WakuCallBack) callback, resp) ); } + void cGoWakuGetMyENR(void* ctx, void* resp) { + WAKU_CALL (waku_get_my_enr(ctx, (WakuCallBack) callback, resp) ); + } + */ import "C" @@ -446,6 +451,20 @@ func (self *WakuNode) WakuListenAddresses() (string, error) { return "", errors.New(errMsg) } +func (self *WakuNode) WakuGetMyENR() (string, error) { + var resp = C.allocResp() + defer C.freeResp(resp) + C.cGoWakuGetMyENR(self.ctx, resp) + + if C.getRet(resp) == C.RET_OK { + var myENR = C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp))) + return myENR, nil + } + errMsg := "error WakuGetMyENR: " + + C.GoStringN(C.getMyCharPtr(resp), C.int(C.getMyCharLen(resp))) + return "", errors.New(errMsg) +} + func main() { WakuSetup() @@ -516,11 +535,18 @@ func main() { return } + myENR, err := node.WakuGetMyENR() + if err != nil { + fmt.Println("Error happened:", err.Error()) + return + } + fmt.Println("Version:", version) fmt.Println("Custom content topic:", formattedContentTopic) fmt.Println("Custom pubsub topic:", formattedPubsubTopic) fmt.Println("Default pubsub topic:", defaultPubsubTopic) fmt.Println("Listen addresses:", listenAddresses) + fmt.Println("My ENR:", myENR) // Wait for a SIGINT or SIGTERM signal ch := make(chan os.Signal, 1) diff --git a/examples/publisher.nim b/examples/publisher.nim index e26a476edc..fa646536c9 100644 --- a/examples/publisher.nim +++ b/examples/publisher.nim @@ -46,9 +46,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} = let nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get() ip = parseIpAddress("0.0.0.0") - flags = CapabilitiesBitfield.init( - lightpush = false, filter = false, store = false, relay = true - ) + flags = CapabilitiesBitfield.init(relay = true) var enrBuilder = EnrBuilder.init(nodeKey) diff --git a/examples/subscriber.nim b/examples/subscriber.nim index 477f90982e..2cab3a731e 100644 --- a/examples/subscriber.nim +++ b/examples/subscriber.nim @@ -44,9 +44,7 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} = let nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[] ip = parseIpAddress("0.0.0.0") - flags = CapabilitiesBitfield.init( - lightpush = false, filter = false, store = false, relay = true - ) + flags = CapabilitiesBitfield.init(relay = true) var enrBuilder = EnrBuilder.init(nodeKey) diff --git a/library/callback.nim b/library/callback.nim index 9b5ea09186..8a8522600c 100644 --- a/library/callback.nim +++ b/library/callback.nim @@ -1,3 +1,13 @@ +import ./waku_thread/waku_thread + type WakuCallBack* = proc( callerRet: cint, msg: ptr cchar, len: csize_t, userData: pointer ) {.cdecl, gcsafe, raises: [].} + +template checkLibwakuParams*( + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer +) = + ctx[].userData = userData + + if isNil(callback): + return RET_MISSING_CALLBACK diff --git a/library/libwaku.h b/library/libwaku.h index 94fe03107c..edd69757de 100644 --- a/library/libwaku.h +++ b/library/libwaku.h @@ -7,6 +7,7 @@ #define __libwaku__ #include +#include // The possible returned values for the functions that return int #define RET_OK 0 @@ -75,6 +76,12 @@ int waku_relay_publish(void* ctx, WakuCallBack callback, void* userData); +int waku_lightpush_publish(void* ctx, + const char* pubSubTopic, + const char* jsonWakuMessage, + WakuCallBack callback, + void* userData); + int waku_relay_subscribe(void* ctx, const char* pubSubTopic, WakuCallBack callback, @@ -85,12 +92,38 @@ int waku_relay_unsubscribe(void* ctx, WakuCallBack callback, void* userData); +int waku_relay_get_num_connected_peers(void* ctx, + const char* pubSubTopic, + WakuCallBack callback, + void* userData); + +int waku_relay_get_num_peers_in_mesh(void* ctx, + const char* pubSubTopic, + WakuCallBack callback, + void* userData); + +int waku_store_query(void* ctx, + const char* jsonQuery, + const char* peerAddr, + int timeoutMs, + WakuCallBack callback, + void* userData); + int waku_connect(void* ctx, const char* peerMultiAddr, unsigned int timeoutMs, WakuCallBack callback, void* userData); +int waku_get_peerids_from_peerstore(void* ctx, + WakuCallBack callback, + void* userData); + +int waku_get_peerids_by_protocol(void* ctx, + const char* protocol, + WakuCallBack callback, + void* userData); + int waku_listen_addresses(void* ctx, WakuCallBack callback, void* userData); @@ -114,6 +147,24 @@ int waku_discv5_update_bootnodes(void* ctx, WakuCallBack callback, void* userData); +int waku_start_discv5(void* ctx, + WakuCallBack callback, + void* userData); + +int waku_stop_discv5(void* ctx, + WakuCallBack callback, + void* userData); + +// Retrieves the ENR information +int waku_get_my_enr(void* ctx, + WakuCallBack callback, + void* userData); + +int waku_peer_exchange_request(void* ctx, + int numPeers, + WakuCallBack callback, + void* userData); + #ifdef __cplusplus } #endif diff --git a/library/libwaku.nim b/library/libwaku.nim index f1bc6f3d69..f313d925fe 100644 --- a/library/libwaku.nim +++ b/library/libwaku.nim @@ -5,7 +5,7 @@ when defined(linux): {.passl: "-Wl,-soname,libwaku.so".} -import std/[json, sequtils, atomics, times, strformat, options, atomics, strutils, os] +import std/[json, sequtils, atomics, strformat, options, atomics] import chronicles, chronos import waku/common/base64, @@ -19,6 +19,7 @@ import ./waku_thread/inter_thread_communication/requests/peer_manager_request, ./waku_thread/inter_thread_communication/requests/protocols/relay_request, ./waku_thread/inter_thread_communication/requests/protocols/store_request, + ./waku_thread/inter_thread_communication/requests/protocols/lightpush_request, ./waku_thread/inter_thread_communication/requests/debug_node_request, ./waku_thread/inter_thread_communication/requests/discovery_request, ./waku_thread/inter_thread_communication/waku_thread_request, @@ -51,7 +52,26 @@ template foreignThreadGc(body: untyped) = when declared(tearDownForeignThreadGc): tearDownForeignThreadGc() -proc relayEventCallback(ctx: ptr Context): WakuRelayHandler = +template handleRes[T: string | void]( + res: Result[T, string], callback: WakuCallBack, userData: pointer +) = + ## Handles the Result responses, which can either be Result[string, string] or + ## Result[void, string]. Notice that in case of Result[void, string], it is enough to + ## just return RET_OK and not provide any additional feedback through the callback. + if res.isErr(): + foreignThreadGc: + let msg = "libwaku error: " & $res.error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_ERR + + when T is string: + let msg = $res.get() + if msg.len > 0: + foreignThreadGc: + callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_OK + +proc relayEventCallback(ctx: ptr WakuContext): WakuRelayHandler = return proc( pubsubTopic: PubsubTopic, msg: WakuMessage ): Future[system.void] {.async.} = @@ -143,26 +163,16 @@ proc waku_new( return ctx proc waku_destroy( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = - if isNil(callback): - return RET_MISSING_CALLBACK - - waku_thread.stopWakuThread(ctx).isOkOr: - foreignThreadGc: - let msg = $error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR + checkLibwakuParams(ctx, callback, userData) - return RET_OK + waku_thread.stopWakuThread(ctx).handleRes(callback, userData) proc waku_version( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = - ctx[].userData = userData - - if isNil(callback): - return RET_MISSING_CALLBACK + checkLibwakuParams(ctx, callback, userData) foreignThreadGc: callback( @@ -175,13 +185,13 @@ proc waku_version( return RET_OK proc waku_set_event_callback( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ) {.dynlib, exportc.} = ctx[].eventCallback = cast[pointer](callback) ctx[].eventUserData = userData proc waku_content_topic( - ctx: ptr Context, + ctx: ptr WakuContext, appName: cstring, appVersion: cuint, contentTopicName: cstring, @@ -191,10 +201,7 @@ proc waku_content_topic( ): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding - ctx[].userData = userData - - if isNil(callback): - return RET_MISSING_CALLBACK + checkLibwakuParams(ctx, callback, userData) let appStr = appName.alloc() let ctnStr = contentTopicName.alloc() @@ -212,14 +219,11 @@ proc waku_content_topic( return RET_OK proc waku_pubsub_topic( - ctx: ptr Context, topicName: cstring, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, topicName: cstring, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc, cdecl.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding - ctx[].userData = userData - - if isNil(callback): - return RET_MISSING_CALLBACK + checkLibwakuParams(ctx, callback, userData) let topicNameStr = topicName.alloc() @@ -233,14 +237,11 @@ proc waku_pubsub_topic( return RET_OK proc waku_default_pubsub_topic( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_default_pubsub_topic - ctx[].userData = userData - - if isNil(callback): - return RET_MISSING_CALLBACK + checkLibwakuParams(ctx, callback, userData) callback( RET_OK, @@ -252,7 +253,7 @@ proc waku_default_pubsub_topic( return RET_OK proc waku_relay_publish( - ctx: ptr Context, + ctx: ptr WakuContext, pubSubTopic: cstring, jsonWakuMessage: cstring, timeoutMs: cuint, @@ -261,10 +262,7 @@ proc waku_relay_publish( ): cint {.dynlib, exportc, cdecl.} = # https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms - ctx[].userData = userData - - if isNil(callback): - return RET_MISSING_CALLBACK + checkLibwakuParams(ctx, callback, userData) let jwm = jsonWakuMessage.alloc() var jsonMessage: JsonMessage @@ -280,11 +278,13 @@ proc waku_relay_publish( deallocShared(jwm) let wakuMessage = jsonMessage.toWakuMessage().valueOr: - let msg = fmt"Problem building the WakuMessage: {error}" + let msg = "Problem building the WakuMessage: " & $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR let pst = pubSubTopic.alloc() + defer: + deallocShared(pst) let targetPubSubTopic = if len(pst) == 0: @@ -292,7 +292,8 @@ proc waku_relay_publish( else: $pst - let sendReqRes = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.RELAY, RelayRequest.createShared( @@ -302,21 +303,12 @@ proc waku_relay_publish( wakuMessage, ), ) - deallocShared(pst) - - if sendReqRes.isErr(): - let msg = $sendReqRes.error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR - - let msgHash = $sendReqRes.value - callback(RET_OK, unsafeAddr msgHash[0], cast[csize_t](len(msgHash)), userData) - return RET_OK + .handleRes(callback, userData) proc waku_start( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) ## TODO: handle the error discard waku_thread.sendRequestToWakuThread( ctx, @@ -325,9 +317,10 @@ proc waku_start( ) proc waku_stop( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) + ## TODO: handle the error discard waku_thread.sendRequestToWakuThread( ctx, @@ -336,36 +329,42 @@ proc waku_stop( ) proc waku_relay_subscribe( - ctx: ptr Context, pubSubTopic: cstring, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, + pubSubTopic: cstring, + callback: WakuCallBack, + userData: pointer, ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) let pst = pubSubTopic.alloc() + defer: + deallocShared(pst) var cb = relayEventCallback(ctx) - let sendReqRes = waku_thread.sendRequestToWakuThread( + + waku_thread + .sendRequestToWakuThread( ctx, RequestType.RELAY, RelayRequest.createShared( RelayMsgType.SUBSCRIBE, PubsubTopic($pst), WakuRelayHandler(cb) ), ) - deallocShared(pst) - - if sendReqRes.isErr(): - let msg = $sendReqRes.error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR - - return RET_OK + .handleRes(callback, userData) proc waku_relay_unsubscribe( - ctx: ptr Context, pubSubTopic: cstring, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, + pubSubTopic: cstring, + callback: WakuCallBack, + userData: pointer, ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) let pst = pubSubTopic.alloc() + defer: + deallocShared(pst) - let sendReqRes = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.RELAY, RelayRequest.createShared( @@ -374,120 +373,266 @@ proc waku_relay_unsubscribe( WakuRelayHandler(relayEventCallback(ctx)), ), ) - deallocShared(pst) + .handleRes(callback, userData) + +proc waku_relay_get_num_connected_peers( + ctx: ptr WakuContext, + pubSubTopic: cstring, + callback: WakuCallBack, + userData: pointer, +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + let pst = pubSubTopic.alloc() + defer: + deallocShared(pst) + + waku_thread + .sendRequestToWakuThread( + ctx, + RequestType.RELAY, + RelayRequest.createShared(RelayMsgType.LIST_CONNECTED_PEERS, PubsubTopic($pst)), + ) + .handleRes(callback, userData) + +proc waku_relay_get_num_peers_in_mesh( + ctx: ptr WakuContext, + pubSubTopic: cstring, + callback: WakuCallBack, + userData: pointer, +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + let pst = pubSubTopic.alloc() + defer: + deallocShared(pst) + + waku_thread + .sendRequestToWakuThread( + ctx, + RequestType.RELAY, + RelayRequest.createShared(RelayMsgType.LIST_MESH_PEERS, PubsubTopic($pst)), + ) + .handleRes(callback, userData) - if sendReqRes.isErr(): - let msg = $sendReqRes.error +proc waku_lightpush_publish( + ctx: ptr WakuContext, + pubSubTopic: cstring, + jsonWakuMessage: cstring, + callback: WakuCallBack, + userData: pointer, +): cint {.dynlib, exportc, cdecl.} = + checkLibwakuParams(ctx, callback, userData) + + let jwm = jsonWakuMessage.alloc() + let pst = pubSubTopic.alloc() + defer: + deallocShared(jwm) + deallocShared(pst) + + var jsonMessage: JsonMessage + try: + let jsonContent = parseJson($jwm) + jsonMessage = JsonMessage.fromJsonNode(jsonContent) + except JsonParsingError: + let msg = fmt"Error parsing json message: {getCurrentExceptionMsg()}" callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR - return RET_OK + let wakuMessage = jsonMessage.toWakuMessage().valueOr: + let msg = "Problem building the WakuMessage: " & $error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_ERR + + let targetPubSubTopic = + if len(pst) == 0: + DefaultPubsubTopic + else: + $pst + + waku_thread + .sendRequestToWakuThread( + ctx, + RequestType.LIGHTPUSH, + LightpushRequest.createShared( + LightpushMsgType.PUBLISH, PubsubTopic($pst), wakuMessage + ), + ) + .handleRes(callback, userData) proc waku_connect( - ctx: ptr Context, + ctx: ptr WakuContext, peerMultiAddr: cstring, timeoutMs: cuint, callback: WakuCallBack, userData: pointer, ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) - let connRes = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.PEER_MANAGER, PeerManagementRequest.createShared( PeerManagementMsgType.CONNECT_TO, $peerMultiAddr, chronos.milliseconds(timeoutMs) ), ) + .handleRes(callback, userData) + +proc waku_get_peerids_from_peerstore( + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + let connRes = waku_thread.sendRequestToWakuThread( + ctx, + RequestType.PEER_MANAGER, + PeerManagementRequest.createShared(PeerManagementMsgType.GET_ALL_PEER_IDS), + ) + if connRes.isErr(): + let msg = $connRes.error + callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_ERR + + let msg = $connRes.value + callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) + return RET_OK + +proc waku_get_peerids_by_protocol( + ctx: ptr WakuContext, protocol: cstring, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + let connRes = waku_thread.sendRequestToWakuThread( + ctx, + RequestType.PEER_MANAGER, + PeerManagementRequest.createGetPeerIdsByProtocolRequest($protocol), + ) if connRes.isErr(): let msg = $connRes.error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR + let msg = $connRes.value + callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_OK proc waku_store_query( - ctx: ptr Context, - queryJson: cstring, - peerId: cstring, + ctx: ptr WakuContext, + jsonQuery: cstring, + peerAddr: cstring, timeoutMs: cint, callback: WakuCallBack, userData: pointer, ): cint {.dynlib, exportc.} = - ctx[].userData = userData - - ## TODO: implement the logic that make the "self" node to act as a Store client + checkLibwakuParams(ctx, callback, userData) - # if sendReqRes.isErr(): - # let msg = $sendReqRes.error - # callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg))) - # return RET_ERR - - return RET_OK + waku_thread + .sendRequestToWakuThread( + ctx, + RequestType.STORE, + JsonStoreQueryRequest.createShared(jsonQuery, peerAddr, timeoutMs), + ) + .handleRes(callback, userData) proc waku_listen_addresses( - ctx: ptr Context, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) - let connRes = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.DEBUG, DebugNodeRequest.createShared(DebugNodeMsgType.RETRIEVE_LISTENING_ADDRESSES), ) - if connRes.isErr(): - let msg = $connRes.error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR - else: - let msg = $connRes.value - callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_OK + .handleRes(callback, userData) proc waku_dns_discovery( - ctx: ptr Context, + ctx: ptr WakuContext, entTreeUrl: cstring, nameDnsServer: cstring, timeoutMs: cint, callback: WakuCallBack, userData: pointer, ): cint {.dynlib, exportc.} = - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) - let bootstrapPeers = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.DISCOVERY, DiscoveryRequest.createRetrieveBootstrapNodesRequest( DiscoveryMsgType.GET_BOOTSTRAP_NODES, entTreeUrl, nameDnsServer, timeoutMs ), - ).valueOr: - let msg = $error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR - - let msg = $bootstrapPeers - callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_OK + ) + .handleRes(callback, userData) proc waku_discv5_update_bootnodes( - ctx: ptr Context, bootnodes: cstring, callback: WakuCallBack, userData: pointer + ctx: ptr WakuContext, bootnodes: cstring, callback: WakuCallBack, userData: pointer ): cint {.dynlib, exportc.} = ## Updates the bootnode list used for discovering new peers via DiscoveryV5 ## bootnodes - JSON array containing the bootnode ENRs i.e. `["enr:...", "enr:..."]` - ctx[].userData = userData + checkLibwakuParams(ctx, callback, userData) - let resp = waku_thread.sendRequestToWakuThread( + waku_thread + .sendRequestToWakuThread( ctx, RequestType.DISCOVERY, DiscoveryRequest.createUpdateBootstrapNodesRequest( DiscoveryMsgType.UPDATE_DISCV5_BOOTSTRAP_NODES, bootnodes ), + ) + .handleRes(callback, userData) + +proc waku_get_my_enr( + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + waku_thread + .sendRequestToWakuThread( + ctx, + RequestType.DEBUG, + DebugNodeRequest.createShared(DebugNodeMsgType.RETRIEVE_MY_ENR), + ) + .handleRes(callback, userData) + +proc waku_start_discv5( + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + waku_thread + .sendRequestToWakuThread( + ctx, RequestType.DISCOVERY, DiscoveryRequest.createDiscV5StartRequest() + ) + .handleRes(callback, userData) + +proc waku_stop_discv5( + ctx: ptr WakuContext, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + waku_thread + .sendRequestToWakuThread( + ctx, RequestType.DISCOVERY, DiscoveryRequest.createDiscV5StopRequest() + ) + .handleRes(callback, userData) + +proc waku_peer_exchange_request( + ctx: ptr WakuContext, numPeers: uint64, callback: WakuCallBack, userData: pointer +): cint {.dynlib, exportc.} = + checkLibwakuParams(ctx, callback, userData) + + let discoveredPeers = waku_thread.sendRequestToWakuThread( + ctx, RequestType.DISCOVERY, DiscoveryRequest.createPeerExchangeRequest(numPeers) ).valueOr: let msg = $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR - let msg = $resp + let msg = $discoveredPeers callback(RET_OK, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_OK diff --git a/library/waku_thread/inter_thread_communication/requests/debug_node_request.nim b/library/waku_thread/inter_thread_communication/requests/debug_node_request.nim index c2b36f6fa4..5d470707ac 100644 --- a/library/waku_thread/inter_thread_communication/requests/debug_node_request.nim +++ b/library/waku_thread/inter_thread_communication/requests/debug_node_request.nim @@ -1,9 +1,10 @@ import std/json -import chronicles, chronos, results +import chronicles, chronos, results, eth/p2p/discoveryv5/enr import ../../../../waku/factory/waku, ../../../../waku/node/waku_node type DebugNodeMsgType* = enum RETRIEVE_LISTENING_ADDRESSES + RETRIEVE_MY_ENR type DebugNodeRequest* = object operation: DebugNodeMsgType @@ -28,5 +29,7 @@ proc process*( case self.operation of RETRIEVE_LISTENING_ADDRESSES: return ok($(%*waku.node.getMultiaddresses())) + of RETRIEVE_MY_ENR: + return ok($(%*waku.node.enr.toURI())) return err("unsupported operation in DebugNodeRequest") diff --git a/library/waku_thread/inter_thread_communication/requests/discovery_request.nim b/library/waku_thread/inter_thread_communication/requests/discovery_request.nim index d7c58835fb..e23b9be26a 100644 --- a/library/waku_thread/inter_thread_communication/requests/discovery_request.nim +++ b/library/waku_thread/inter_thread_communication/requests/discovery_request.nim @@ -4,12 +4,17 @@ import ../../../../waku/factory/waku, ../../../../waku/discovery/waku_dnsdisc, ../../../../waku/discovery/waku_discv5, + ../../../../waku/waku_peer_exchange, ../../../../waku/waku_core/peers, + ../../../../waku/node/waku_node, ../../../alloc type DiscoveryMsgType* = enum GET_BOOTSTRAP_NODES UPDATE_DISCV5_BOOTSTRAP_NODES + START_DISCV5 + STOP_DISCV5 + PEER_EXCHANGE type DiscoveryRequest* = object operation: DiscoveryMsgType @@ -22,6 +27,9 @@ type DiscoveryRequest* = object ## used in UPDATE_DISCV5_BOOTSTRAP_NODES nodes: cstring + ## used in PEER_EXCHANGE + numPeers: uint64 + proc createShared( T: type DiscoveryRequest, op: DiscoveryMsgType, @@ -29,6 +37,7 @@ proc createShared( nameDnsServer: cstring, timeoutMs: cint, nodes: cstring, + numPeers: uint64, ): ptr type T = var ret = createShared(T) ret[].operation = op @@ -36,6 +45,7 @@ proc createShared( ret[].nameDnsServer = nameDnsServer.alloc() ret[].timeoutMs = timeoutMs ret[].nodes = nodes.alloc() + ret[].numPeers = numPeers return ret proc createRetrieveBootstrapNodesRequest*( @@ -45,16 +55,28 @@ proc createRetrieveBootstrapNodesRequest*( nameDnsServer: cstring, timeoutMs: cint, ): ptr type T = - return T.createShared(op, enrTreeUrl, nameDnsServer, timeoutMs, "") + return T.createShared(op, enrTreeUrl, nameDnsServer, timeoutMs, "", 0) proc createUpdateBootstrapNodesRequest*( T: type DiscoveryRequest, op: DiscoveryMsgType, nodes: cstring ): ptr type T = - return T.createShared(op, "", "", 0, nodes) + return T.createShared(op, "", "", 0, nodes, 0) + +proc createDiscV5StartRequest*(T: type DiscoveryRequest): ptr type T = + return T.createShared(START_DISCV5, "", "", 0, "", 0) + +proc createDiscV5StopRequest*(T: type DiscoveryRequest): ptr type T = + return T.createShared(STOP_DISCV5, "", "", 0, "", 0) + +proc createPeerExchangeRequest*( + T: type DiscoveryRequest, numPeers: uint64 +): ptr type T = + return T.createShared(PEER_EXCHANGE, "", "", 0, "", numPeers) proc destroyShared(self: ptr DiscoveryRequest) = deallocShared(self[].enrTreeUrl) deallocShared(self[].nameDnsServer) + deallocShared(self[].nodes) deallocShared(self) proc retrieveBootstrapNodes( @@ -79,6 +101,12 @@ proc updateDiscv5BootstrapNodes(nodes: string, waku: ptr Waku): Result[void, str return err("error in updateDiscv5BootstrapNodes: " & $error) return ok() +proc performPeerExchangeRequestTo( + numPeers: uint64, waku: ptr Waku +): Future[Result[int, string]] {.async.} = + return (await waku.node.fetchPeerExchangePeers(numPeers)).isOkOr: + return err($error) + proc process*( self: ptr DiscoveryRequest, waku: ptr Waku ): Future[Result[string, string]] {.async.} = @@ -86,6 +114,16 @@ proc process*( destroyShared(self) case self.operation + of START_DISCV5: + let res = await waku.wakuDiscv5.start() + res.isOkOr: + return err($error) + + return ok("discv5 started correctly") + of STOP_DISCV5: + await waku.wakuDiscv5.stop() + + return ok("discv5 stopped correctly") of GET_BOOTSTRAP_NODES: let nodes = retrieveBootstrapNodes($self[].enrTreeUrl, $self[].nameDnsServer).valueOr: return err($error) @@ -94,6 +132,11 @@ proc process*( of UPDATE_DISCV5_BOOTSTRAP_NODES: updateDiscv5BootstrapNodes($self[].nodes, waku).isOkOr: return err($error) + return ok("discovery request processed correctly") + of PEER_EXCHANGE: + let numValidPeers = (await performPeerExchangeRequestTo(self[].numPeers, waku)).valueOr: + return err("error calling performPeerExchangeRequestTo: " & $error) + return ok($numValidPeers) return err("discovery request not handled") diff --git a/library/waku_thread/inter_thread_communication/requests/peer_manager_request.nim b/library/waku_thread/inter_thread_communication/requests/peer_manager_request.nim index 0c4b60b970..439859c6f0 100644 --- a/library/waku_thread/inter_thread_communication/requests/peer_manager_request.nim +++ b/library/waku_thread/inter_thread_communication/requests/peer_manager_request.nim @@ -1,20 +1,27 @@ import std/[sequtils, strutils] import chronicles, chronos, results -import ../../../../waku/factory/waku, ../../../../waku/node/waku_node, ../../../alloc +import + ../../../../waku/factory/waku, + ../../../../waku/node/waku_node, + ../../../alloc, + ../../../../waku/node/peer_manager -type PeerManagementMsgType* = enum +type PeerManagementMsgType* {.pure.} = enum CONNECT_TO + GET_ALL_PEER_IDS + GET_PEER_IDS_BY_PROTOCOL type PeerManagementRequest* = object operation: PeerManagementMsgType peerMultiAddr: cstring dialTimeout: Duration + protocol: cstring proc createShared*( T: type PeerManagementRequest, op: PeerManagementMsgType, - peerMultiAddr: string, - dialTimeout: Duration, + peerMultiAddr = "", + dialTimeout = chronos.milliseconds(0), ## arbitrary Duration as not all ops needs dialTimeout ): ptr type T = var ret = createShared(T) ret[].operation = op @@ -22,8 +29,21 @@ proc createShared*( ret[].dialTimeout = dialTimeout return ret +proc createGetPeerIdsByProtocolRequest*( + T: type PeerManagementRequest, protocol = "" +): ptr type T = + var ret = createShared(T) + ret[].operation = PeerManagementMsgType.GET_PEER_IDS_BY_PROTOCOL + ret[].protocol = protocol.alloc() + return ret + proc destroyShared(self: ptr PeerManagementRequest) = - deallocShared(self[].peerMultiAddr) + if not isNil(self[].peerMultiAddr): + deallocShared(self[].peerMultiAddr) + + if not isNil(self[].protocol): + deallocShared(self[].protocol) + deallocShared(self) proc connectTo( @@ -53,5 +73,14 @@ proc process*( let ret = waku.node.connectTo($self[].peerMultiAddr, self[].dialTimeout) if ret.isErr(): return err(ret.error) + of GET_ALL_PEER_IDS: + ## returns a comma-separated string of peerIDs + let peerIDs = waku.node.peerManager.peerStore.peers().mapIt($it.peerId).join(",") + return ok(peerIDs) + of GET_PEER_IDS_BY_PROTOCOL: + ## returns a comma-separated string of peerIDs that mount the given protocol + let (inPeers, outPeers) = waku.node.peerManager.connectedPeers($self[].protocol) + let allPeerIDs = inPeers & outPeers + return ok(allPeerIDs.mapIt(it.hex()).join(",")) return ok("") diff --git a/library/waku_thread/inter_thread_communication/requests/protocols/lightpush_request.nim b/library/waku_thread/inter_thread_communication/requests/protocols/lightpush_request.nim new file mode 100644 index 0000000000..810e938364 --- /dev/null +++ b/library/waku_thread/inter_thread_communication/requests/protocols/lightpush_request.nim @@ -0,0 +1,103 @@ +import std/net, options +import chronicles, chronos, stew/byteutils, results +import + ../../../../../waku/waku_core/message/message, + ../../../../../waku/factory/waku, + ../../../../../waku/waku_core/message, + ../../../../../waku/waku_core/time, # Timestamp + ../../../../../waku/waku_core/topics/pubsub_topic, + ../../../../../waku/waku_lightpush/client, + ../../../../../waku/waku_lightpush/common, + ../../../../../waku/node/peer_manager/peer_manager, + ../../../../alloc + +type LightpushMsgType* = enum + PUBLISH + +type ThreadSafeWakuMessage* = object + payload: SharedSeq[byte] + contentTopic: cstring + meta: SharedSeq[byte] + version: uint32 + timestamp: Timestamp + ephemeral: bool + when defined(rln): + proof: SharedSeq[byte] + +type LightpushRequest* = object + operation: LightpushMsgType + pubsubTopic: cstring + message: ThreadSafeWakuMessage # only used in 'PUBLISH' requests + +proc createShared*( + T: type LightpushRequest, + op: LightpushMsgType, + pubsubTopic: PubsubTopic, + m = WakuMessage(), +): ptr type T = + var ret = createShared(T) + ret[].operation = op + ret[].pubsubTopic = pubsubTopic.alloc() + ret[].message = ThreadSafeWakuMessage( + payload: allocSharedSeq(m.payload), + contentTopic: m.contentTopic.alloc(), + meta: allocSharedSeq(m.meta), + version: m.version, + timestamp: m.timestamp, + ephemeral: m.ephemeral, + ) + when defined(rln): + ret[].message.proof = allocSharedSeq(m.proof) + + return ret + +proc destroyShared(self: ptr LightpushRequest) = + deallocSharedSeq(self[].message.payload) + deallocShared(self[].message.contentTopic) + deallocSharedSeq(self[].message.meta) + when defined(rln): + deallocSharedSeq(self[].message.proof) + + deallocShared(self) + +proc toWakuMessage(m: ThreadSafeWakuMessage): WakuMessage = + var wakuMessage = WakuMessage() + + wakuMessage.payload = m.payload.toSeq() + wakuMessage.contentTopic = $m.contentTopic + wakuMessage.meta = m.meta.toSeq() + wakuMessage.version = m.version + wakuMessage.timestamp = m.timestamp + wakuMessage.ephemeral = m.ephemeral + + when defined(rln): + wakuMessage.proof = m.proof + + return wakuMessage + +proc process*( + self: ptr LightpushRequest, waku: ptr Waku +): Future[Result[string, string]] {.async.} = + defer: + destroyShared(self) + + case self.operation + of PUBLISH: + let msg = self.message.toWakuMessage() + let pubsubTopic = $self.pubsubTopic + + if waku.node.wakuLightpushClient.isNil(): + return err("LightpushRequest waku.node.wakuLightpushClient is nil") + + let peerOpt = waku.node.peerManager.selectPeer(WakuLightPushCodec) + if peerOpt.isNone(): + return err("failed to lightpublish message, no suitable remote peers") + + ( + await waku.node.wakuLightpushClient.publish( + pubsubTopic, msg, peer = peerOpt.get() + ) + ).isOkOr: + return err("LightpushRequest error publishing: " & $error) + + return ok("") diff --git a/library/waku_thread/inter_thread_communication/requests/protocols/relay_request.nim b/library/waku_thread/inter_thread_communication/requests/protocols/relay_request.nim index b560d1d765..ca748889e2 100644 --- a/library/waku_thread/inter_thread_communication/requests/protocols/relay_request.nim +++ b/library/waku_thread/inter_thread_communication/requests/protocols/relay_request.nim @@ -13,6 +13,10 @@ type RelayMsgType* = enum SUBSCRIBE UNSUBSCRIBE PUBLISH + LIST_CONNECTED_PEERS + ## to return the list of all connected peers to an specific pubsub topic + LIST_MESH_PEERS + ## to return the list of only the peers that conform the mesh for a particular pubsub topic type ThreadSafeWakuMessage* = object payload: SharedSeq[byte] @@ -104,5 +108,13 @@ proc process*( elif numPeers > 0: let msgHash = computeMessageHash(pubSubTopic, msg).to0xHex return ok(msgHash) + of LIST_CONNECTED_PEERS: + let numConnPeers = waku.node.wakuRelay.getNumConnectedPeers($self.pubsubTopic).valueOr: + return err($error) + return ok($numConnPeers) + of LIST_MESH_PEERS: + let numPeersInMesh = waku.node.wakuRelay.getNumPeersInMesh($self.pubsubTopic).valueOr: + return err($error) + return ok($numPeersInMesh) return ok("") diff --git a/library/waku_thread/inter_thread_communication/requests/protocols/store_request.nim b/library/waku_thread/inter_thread_communication/requests/protocols/store_request.nim index da611915fb..2f6f8ae98d 100644 --- a/library/waku_thread/inter_thread_communication/requests/protocols/store_request.nim +++ b/library/waku_thread/inter_thread_communication/requests/protocols/store_request.nim @@ -1,52 +1,138 @@ +import std/[json, sugar, options] import chronos, results -import ../../../../../waku/factory/waku, ../../../../alloc, ../../../../callback +import + ../../../../../waku/factory/waku, + ../../../../alloc, + ../../../../../waku/waku_core/peers, + ../../../../../waku/waku_core/time, + ../../../../../waku/waku_core/message/digest, + ../../../../../waku/waku_store/common, + ../../../../../waku/waku_store/client, + ../../../../../waku/common/paging type StoreReqType* = enum REMOTE_QUERY ## to perform a query to another Store node - LOCAL_QUERY ## to retrieve the data from 'self' node -type StoreQueryRequest* = object - queryJson: cstring +type JsonStoreQueryRequest* = object + jsonQuery: cstring peerAddr: cstring timeoutMs: cint - storeCallback: WakuCallBack type StoreRequest* = object operation: StoreReqType storeReq: pointer -proc createShared*( - T: type StoreRequest, operation: StoreReqType, request: pointer -): ptr type T = - var ret = createShared(T) - ret[].request = request - return ret +func fromJsonNode( + T: type JsonStoreQueryRequest, jsonContent: JsonNode +): StoreQueryRequest = + let contentTopics = collect(newSeq): + for cTopic in jsonContent["content_topics"].getElems(): + cTopic.getStr() + + let msgHashes = collect(newSeq): + for hashJsonObj in jsonContent["message_hashes"].getElems(): + var hash: WakuMessageHash + var count: int = 0 + for byteValue in hashJsonObj.getElems(): + hash[count] = byteValue.getInt().byte + count.inc() + + hash + + let pubsubTopic = + if jsonContent.contains("pubsub_topic"): + some(jsonContent["pubsub_topic"].getStr()) + else: + none(string) + + let startTime = + if jsonContent.contains("time_start"): + some(Timestamp(jsonContent["time_start"].getInt())) + else: + none(Timestamp) + + let endTime = + if jsonContent.contains("time_end"): + some(Timestamp(jsonContent["time_end"].getInt())) + else: + none(Timestamp) + + let paginationCursor = + if jsonContent.contains("pagination_cursor"): + var hash: WakuMessageHash + var count: int = 0 + for byteValue in jsonContent["pagination_cursor"].getElems(): + hash[count] = byteValue.getInt().byte + count.inc() + + some(hash) + else: + none(WakuMessageHash) + + let paginationForwardBool = jsonContent["pagination_forward"].getBool() + let paginationForward = + if paginationForwardBool: PagingDirection.FORWARD else: PagingDirection.BACKWARD + + let paginationLimit = + if jsonContent.contains("pagination_limit"): + some(uint64(jsonContent["pagination_limit"].getInt())) + else: + none(uint64) + + return StoreQueryRequest( + requestId: jsonContent["request_id"].getStr(), + includeData: jsonContent["include_data"].getBool(), + pubsubTopic: pubsubTopic, + contentTopics: contentTopics, + startTime: startTime, + endTime: endTime, + messageHashes: msgHashes, + paginationCursor: paginationCursor, + paginationForward: paginationForward, + paginationLimit: paginationLimit, + ) proc createShared*( - T: type StoreQueryRequest, - queryJson: cstring, + T: type JsonStoreQueryRequest, + jsonQuery: cstring, peerAddr: cstring, timeoutMs: cint, - storeCallback: WakuCallBack = nil, ): ptr type T = var ret = createShared(T) ret[].timeoutMs = timeoutMs - ret[].queryJson = queryJson.alloc() + ret[].jsonQuery = jsonQuery.alloc() ret[].peerAddr = peerAddr.alloc() - ret[].storeCallback = storeCallback return ret -proc destroyShared(self: ptr StoreQueryRequest) = - deallocShared(self[].queryJson) +proc destroyShared(self: ptr JsonStoreQueryRequest) = + deallocShared(self[].jsonQuery) deallocShared(self[].peerAddr) deallocShared(self) proc process( - self: ptr StoreQueryRequest, waku: ptr Waku + self: ptr JsonStoreQueryRequest, waku: ptr Waku ): Future[Result[string, string]] {.async.} = defer: destroyShared(self) + let jsonContentRes = catch: + parseJson($self[].jsonQuery) + + if jsonContentRes.isErr(): + return err( + "JsonStoreQueryRequest failed parsing store request: " & jsonContentRes.error.msg + ) + + let storeQueryRequest = JsonStoreQueryRequest.fromJsonNode(jsonContentRes.get()) + + let peer = peers.parsePeerInfo($self[].peerAddr).valueOr: + return err("JsonStoreQueryRequest failed to parse peer addr: " & $error) + + let queryResponse = (await waku.node.wakuStoreClient.query(storeQueryRequest, peer)).valueOr: + return err("JsonStoreQueryRequest failed store query: " & $error) + + return ok($(%*queryResponse)) ## returning the response in json format + proc process*( self: ptr StoreRequest, waku: ptr Waku ): Future[Result[string, string]] {.async.} = @@ -55,9 +141,6 @@ proc process*( case self.operation of REMOTE_QUERY: - return await cast[ptr StoreQueryRequest](self[].storeReq).process(waku) - of LOCAL_QUERY: - discard - # cast[ptr StoreQueryRequest](request[].reqContent).process(node) + return await cast[ptr JsonStoreQueryRequest](self[].storeReq).process(waku) - return ok("") + return err("store request not handled at all") diff --git a/library/waku_thread/inter_thread_communication/waku_thread_request.nim b/library/waku_thread/inter_thread_communication/waku_thread_request.nim index 749eac1e91..4f1733047b 100644 --- a/library/waku_thread/inter_thread_communication/waku_thread_request.nim +++ b/library/waku_thread/inter_thread_communication/waku_thread_request.nim @@ -10,6 +10,7 @@ import ./requests/peer_manager_request, ./requests/protocols/relay_request, ./requests/protocols/store_request, + ./requests/protocols/lightpush_request, ./requests/debug_node_request, ./requests/discovery_request @@ -20,6 +21,7 @@ type RequestType* {.pure.} = enum STORE DEBUG DISCOVERY + LIGHTPUSH type InterThreadRequest* = object reqType: RequestType @@ -56,6 +58,8 @@ proc process*( cast[ptr DebugNodeRequest](request[].reqContent).process(waku[]) of DISCOVERY: cast[ptr DiscoveryRequest](request[].reqContent).process(waku) + of LIGHTPUSH: + cast[ptr LightpushRequest](request[].reqContent).process(waku) return await retFut diff --git a/library/waku_thread/waku_thread.nim b/library/waku_thread/waku_thread.nim index b14faf6bdb..5967cbfddc 100644 --- a/library/waku_thread/waku_thread.nim +++ b/library/waku_thread/waku_thread.nim @@ -9,8 +9,8 @@ import ./inter_thread_communication/waku_thread_request, ./inter_thread_communication/waku_thread_response -type Context* = object - thread: Thread[(ptr Context)] +type WakuContext* = object + thread: Thread[(ptr WakuContext)] reqChannel: ChannelSPSCSingle[ptr InterThreadRequest] reqSignal: ThreadSignalPtr respChannel: ChannelSPSCSingle[ptr InterThreadResponse] @@ -26,7 +26,7 @@ const versionString = "version / git commit hash: " & waku.git_version # TODO: this should be part of the context so multiple instances can be executed var running: Atomic[bool] -proc runWaku(ctx: ptr Context) {.async.} = +proc runWaku(ctx: ptr WakuContext) {.async.} = ## This is the worker body. This runs the Waku node ## and attends library user requests (stop, connect_to, etc.) info "Starting Waku", version = versionString @@ -49,14 +49,14 @@ proc runWaku(ctx: ptr Context) {.async.} = discard ctx.respChannel.trySend(threadSafeResp) discard ctx.respSignal.fireSync() -proc run(ctx: ptr Context) {.thread.} = +proc run(ctx: ptr WakuContext) {.thread.} = ## Launch waku worker waitFor runWaku(ctx) -proc createWakuThread*(): Result[ptr Context, string] = +proc createWakuThread*(): Result[ptr WakuContext, string] = ## This proc is called from the main thread and it creates ## the Waku working thread. - var ctx = createShared(Context, 1) + var ctx = createShared(WakuContext, 1) ctx.reqSignal = ThreadSignalPtr.new().valueOr: return err("couldn't create reqSignal ThreadSignalPtr") ctx.respSignal = ThreadSignalPtr.new().valueOr: @@ -74,7 +74,7 @@ proc createWakuThread*(): Result[ptr Context, string] = return ok(ctx) -proc stopWakuThread*(ctx: ptr Context): Result[void, string] = +proc stopWakuThread*(ctx: ptr WakuContext): Result[void, string] = running.store(false) let fireRes = ctx.reqSignal.fireSync() if fireRes.isErr(): @@ -86,7 +86,7 @@ proc stopWakuThread*(ctx: ptr Context): Result[void, string] = return ok() proc sendRequestToWakuThread*( - ctx: ptr Context, reqType: RequestType, reqContent: pointer + ctx: ptr WakuContext, reqType: RequestType, reqContent: pointer ): Result[string, string] = let req = InterThreadRequest.createShared(reqType, reqContent) ## Sending the request diff --git a/metrics/waku-fleet-dashboard.json b/metrics/waku-fleet-dashboard.json index 2bff016ac6..0916f14d49 100644 --- a/metrics/waku-fleet-dashboard.json +++ b/metrics/waku-fleet-dashboard.json @@ -102,7 +102,7 @@ }, "gridPos": { "h": 10, - "w": 8, + "w": 4, "x": 0, "y": 1 }, @@ -127,10 +127,12 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, + "editorMode": "code", "exemplar": true, "expr": "libp2p_pubsub_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", "legendFormat": "{{fleet}}: {{datacenter}}", + "range": true, "refId": "A" } ], @@ -163,8 +165,8 @@ }, "gridPos": { "h": 10, - "w": 8, - "x": 8, + "w": 7, + "x": 4, "y": 1 }, "id": 46, @@ -236,7 +238,7 @@ "gridPos": { "h": 10, "w": 8, - "x": 16, + "x": 11, "y": 1 }, "id": 58, @@ -277,7 +279,115 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "source" + }, + "properties": [ + { + "id": "custom.width", + "value": 122 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 181 + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 5, + "x": 19, + "y": 1 + }, + "id": 85, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.2.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": false, + "expr": "waku_version{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "Version", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "version", + "source", + "Time" + ] + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, "fieldConfig": { "defaults": { "color": { @@ -290,7 +400,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 10, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -303,7 +413,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "never", + "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", @@ -313,7 +423,6 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", @@ -327,8 +436,7 @@ "value": 80 } ] - }, - "unit": "short" + } }, "overrides": [] }, @@ -338,7 +446,7 @@ "x": 0, "y": 11 }, - "id": 78, + "id": 81, "options": { "legend": { "calcs": [], @@ -351,7 +459,6 @@ "sort": "none" } }, - "pluginVersion": "8.3.1", "targets": [ { "datasource": { @@ -359,15 +466,13 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": true, - "expr": "(increase(libp2p_network_bytes_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", direction=\"in\"}[15m]))/1000000/(900)*8", - "interval": "", - "legendFormat": "{{instance}}:{{direction}}", + "expr": "libp2p_autonat_reachability_confidence{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", reachability=\"Reachable\"}", + "legendFormat": "{{instance}}:{{reachability}}", "range": true, "refId": "A" } ], - "title": "Inbound Traffic (Mbps)", + "title": "Node is Reachable (Experimental)", "type": "timeseries" }, { @@ -375,7 +480,6 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -388,7 +492,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 10, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -401,7 +505,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "never", + "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", @@ -411,7 +515,6 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", @@ -425,8 +528,7 @@ "value": 80 } ] - }, - "unit": "short" + } }, "overrides": [] }, @@ -436,7 +538,7 @@ "x": 12, "y": 11 }, - "id": 79, + "id": 82, "options": { "legend": { "calcs": [], @@ -449,7 +551,6 @@ "sort": "none" } }, - "pluginVersion": "8.3.1", "targets": [ { "datasource": { @@ -457,15 +558,13 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": true, - "expr": "(increase(libp2p_network_bytes_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", direction=\"out\"}[15m]))/1000000/(900)*8", - "interval": "", - "legendFormat": "{{instance}}:{{direction}}", + "expr": "libp2p_autonat_reachability_confidence{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", reachability=\"NotReachable\"}", + "legendFormat": "{{instance}}:{{reachability}}", "range": true, "refId": "A" } ], - "title": "Outbound Traffic (Mbps)", + "title": "Node is NotReachable (Experimental)", "type": "timeseries" }, { @@ -515,7 +614,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -523,34 +623,9 @@ } ] }, - "unit": "short" + "unit": "binBps" }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "relay: node-01.do-ams3.status.prod" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] + "overrides": [] }, "gridPos": { "h": 9, @@ -558,7 +633,9 @@ "x": 0, "y": 20 }, - "id": 11, + "id": 78, + "interval": "15s", + "maxDataPoints": 1000, "options": { "legend": { "calcs": [], @@ -580,14 +657,14 @@ }, "editorMode": "code", "exemplar": true, - "expr": "(increase(waku_node_messages_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m]))", + "expr": "rate(libp2p_network_bytes_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", direction=\"in\"}[$__rate_interval])", "interval": "", - "legendFormat": "{{type}}: {{instance}}", + "legendFormat": "{{instance}}:{{direction}}", "range": true, "refId": "A" } ], - "title": "Messages (1m rate)", + "title": "Inbound Traffic", "type": "timeseries" }, { @@ -595,6 +672,7 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -607,7 +685,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 9, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, @@ -620,7 +698,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", @@ -630,19 +708,22 @@ "mode": "off" } }, + "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "binBps" }, "overrides": [] }, @@ -652,7 +733,7 @@ "x": 12, "y": 20 }, - "id": 54, + "id": 79, "options": { "legend": { "calcs": [], @@ -665,6 +746,7 @@ "sort": "none" } }, + "pluginVersion": "8.3.1", "targets": [ { "datasource": { @@ -672,43 +754,15 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": false, - "expr": "sum by (type)(increase(waku_peers_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", + "exemplar": true, + "expr": "rate(libp2p_network_bytes_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", direction=\"out\"}[$__rate_interval])", "interval": "", - "legendFormat": "peer {{type}}", + "legendFormat": "{{instance}}:{{direction}}", "range": true, "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum by (type)(increase(waku_store_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", - "hide": false, - "interval": "", - "legendFormat": "store {{type}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum by (type)(increase(waku_node_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", - "hide": false, - "interval": "", - "legendFormat": "node {{type}}", - "range": true, - "refId": "C" } ], - "title": "Waku Errors", + "title": "Outbound Traffic", "type": "timeseries" }, { @@ -716,6 +770,7 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -756,33 +811,39 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "binBps" }, "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 12, "x": 0, "y": 29 }, - "id": 66, + "id": 124, "options": { "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true }, "tooltip": { - "mode": "single", + "mode": "multi", "sort": "none" } }, @@ -790,16 +851,18 @@ { "datasource": { "type": "prometheus", - "uid": "P6693426190CB2316" + "uid": "PBFA97CFB590B2093" }, - "exemplar": true, - "expr": "count(count by (contentTopic)(waku_node_messages_total))", - "interval": "", - "legendFormat": "content topics", + "editorMode": "code", + "exemplar": false, + "expr": "avg by(topic, type)(rate(waku_relay_network_bytes_total{direction=\"in\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "{{topic}} - {{type}}", + "range": true, "refId": "A" } ], - "title": "Total Content Topics", + "title": "Relay traffic per shard (in) - average of all peers", "type": "timeseries" }, { @@ -807,6 +870,7 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -819,18 +883,15 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 5, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "stepBefore", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 2, + "lineInterpolation": "linear", + "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" @@ -850,87 +911,159 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "binBps" }, "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 12, "x": 12, "y": 29 }, - "id": 68, + "id": 126, "options": { "legend": { - "calcs": [], + "calcs": [ + "mean" + ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", - "sort": "desc" + "sort": "none" } }, - "pluginVersion": "9.2.5", "targets": [ { "datasource": { "type": "prometheus", - "uid": "P6693426190CB2316" + "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "exemplar": true, - "expr": "waku_version{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{instance}}:{{version}}", + "expr": "avg by(topic, type)(rate(waku_relay_network_bytes_total{direction=\"out\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval]))", + "legendFormat": "{{topic}}", "range": true, "refId": "A" } ], - "title": "Waku version", + "title": "Relay traffic per shard (out) - average of all peers", "type": "timeseries" }, { - "collapsed": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 37 - }, - "id": 17, - "panels": [], + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 39 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, + "editorMode": "code", + "exemplar": true, + "expr": "(increase(waku_node_messages_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m]))", + "interval": "", + "legendFormat": "{{type}}: {{instance}}", + "range": true, "refId": "A" } ], - "title": "General", - "type": "row" + "title": "Messages (1m rate)", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -943,7 +1076,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 4, "gradientMode": "none", "hideFrom": { "legend": false, @@ -971,7 +1104,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -983,12 +1117,12 @@ "overrides": [] }, "gridPos": { - "h": 6, + "h": 9, "w": 12, - "x": 0, - "y": 38 + "x": 12, + "y": 39 }, - "id": 48, + "id": 54, "options": { "legend": { "calcs": [], @@ -997,7 +1131,7 @@ "showLegend": true }, "tooltip": { - "mode": "single", + "mode": "multi", "sort": "none" } }, @@ -1007,14 +1141,16 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "waku_node_filters{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "editorMode": "code", + "exemplar": false, + "expr": "libp2p_pubsub_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", - "legendFormat": "{{instance}}", + "legendFormat": "peer {{instance}}", + "range": true, "refId": "A" } ], - "title": "Waku Node Filters", + "title": "Connected Peers", "type": "timeseries" }, { @@ -1062,7 +1198,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -1074,12 +1211,12 @@ "overrides": [] }, "gridPos": { - "h": 6, + "h": 8, "w": 12, - "x": 12, - "y": 38 + "x": 0, + "y": 48 }, - "id": 50, + "id": 66, "options": { "legend": { "calcs": [], @@ -1099,13 +1236,13 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "waku_node_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "expr": "count(count by (contentTopic)(waku_node_messages_total))", "interval": "", - "legendFormat": "{{type}}: {{instance}}", + "legendFormat": "content topics", "refId": "A" } ], - "title": "Waku Node Errors", + "title": "Total Content Topics", "type": "timeseries" }, { @@ -1125,7 +1262,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 9, "gradientMode": "none", "hideFrom": { "legend": false, @@ -1153,7 +1290,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -1165,12 +1303,12 @@ "overrides": [] }, "gridPos": { - "h": 6, + "h": 9, "w": 12, - "x": 0, - "y": 44 + "x": 12, + "y": 48 }, - "id": 60, + "id": 122, "options": { "legend": { "calcs": [], @@ -1189,10 +1327,12 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "libp2p_pubsub_topics {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "editorMode": "code", + "exemplar": false, + "expr": "sum by (type)(increase(waku_peers_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", "interval": "", - "legendFormat": "Topics: {{instance}}", + "legendFormat": "peer {{type}}", + "range": true, "refId": "A" }, { @@ -1200,11 +1340,13 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "libp2p_pubsub_subscriptions_total {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "editorMode": "code", + "exemplar": false, + "expr": "sum by (type)(increase(waku_store_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", "hide": false, "interval": "", - "legendFormat": "Subscriptions: {{instance}}", + "legendFormat": "store {{type}}", + "range": true, "refId": "B" }, { @@ -1212,258 +1354,312 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "libp2p_pubsub_unsubscriptions_total {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "editorMode": "code", + "exemplar": false, + "expr": "sum by (type)(increase(waku_node_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval]))", "hide": false, "interval": "", - "legendFormat": "Unsubscriptions: {{instance}}", + "legendFormat": "node {{type}}", + "range": true, "refId": "C" } ], - "title": "Pubsub Topics", + "title": "Waku Errors", "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { - "links": [] + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepBefore", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } }, "overrides": [] }, - "fill": 5, - "fillGradient": 0, "gridPos": { - "h": 6, + "h": 8, "w": 12, "x": 12, - "y": 44 - }, - "hiddenSeries": false, - "id": 8, - "legend": { - "alignAsTable": false, - "avg": true, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true + "y": 57 }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", + "id": 68, "options": { - "alertThreshold": true + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } }, - "percentage": false, "pluginVersion": "9.2.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (instance)(libp2p_pubsub_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "editorMode": "code", + "exemplar": true, + "expr": "waku_version{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" + "legendFormat": "{{instance}}:{{version}}", + "range": true, + "refId": "A" } ], - "thresholds": [], - "timeRegions": [], - "title": "LibP2P PubSub Peers", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" + "title": "Waku version", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 65 }, - "yaxes": [ - { - "$$hashKey": "object:1232", - "format": "short", - "logBase": 1, - "show": true - }, + "id": 17, + "panels": [], + "targets": [ { - "$$hashKey": "object:1233", - "format": "short", - "logBase": 1, - "show": true + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "refId": "A" } ], - "yaxis": { - "align": false - } + "title": "General", + "type": "row" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, + "description": "", "fieldConfig": { "defaults": { - "links": [] + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } }, "overrides": [] }, - "fill": 5, - "fillGradient": 0, "gridPos": { "h": 6, "w": 12, "x": 0, - "y": 50 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "alignAsTable": false, - "avg": true, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true + "y": 66 }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", + "id": 48, "options": { - "alertThreshold": true + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "percentage": false, - "pluginVersion": "9.2.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (instance)(libp2p_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "exemplar": true, + "expr": "waku_node_filters{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", "legendFormat": "{{instance}}", "refId": "A" } ], - "thresholds": [], - "timeRegions": [], - "title": "LibP2P Peers", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:1306", - "format": "short", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:1307", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } + "title": "Waku Node Filters", + "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { - "links": [] + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } }, "overrides": [] }, - "fill": 1, - "fillGradient": 0, "gridPos": { "h": 6, "w": 12, "x": 12, - "y": 50 - }, - "hiddenSeries": false, - "id": 9, - "legend": { - "alignAsTable": true, - "avg": true, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": true + "y": 66 }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", + "id": 50, "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.2.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, "targets": [ { "datasource": { @@ -1471,10 +1667,101 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "libp2p_pubsub_validation_success_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", - "hide": false, + "expr": "waku_node_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", - "legendFormat": "success {{instance}}", + "legendFormat": "{{type}}: {{instance}}", + "refId": "A" + } + ], + "title": "Waku Node Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 72 + }, + "id": 60, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "libp2p_pubsub_topics {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "interval": "", + "legendFormat": "Topics: {{instance}}", "refId": "A" }, { @@ -1483,10 +1770,10 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "libp2p_pubsub_validation_failure_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "expr": "libp2p_pubsub_subscriptions_total {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", "hide": false, "interval": "", - "legendFormat": "failure {{instance}}", + "legendFormat": "Subscriptions: {{instance}}", "refId": "B" }, { @@ -1495,44 +1782,15 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "libp2p_pubsub_validation_ignore_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "expr": "libp2p_pubsub_unsubscriptions_total {instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", "hide": false, "interval": "", - "legendFormat": "ignore {{instance}}", + "legendFormat": "Unsubscriptions: {{instance}}", "refId": "C" } ], - "thresholds": [], - "timeRegions": [], - "title": "LibP2P Validations", - "tooltip": { - "shared": true, - "sort": 2, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:189", - "format": "short", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:190", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } + "title": "Pubsub Topics", + "type": "timeseries" }, { "aliasColors": {}, @@ -1549,23 +1807,23 @@ }, "overrides": [] }, - "fill": 1, + "fill": 5, "fillGradient": 0, "gridPos": { "h": 6, "w": 12, - "x": 0, - "y": 56 + "x": 12, + "y": 72 }, "hiddenSeries": false, - "id": 3, + "id": 8, "legend": { "alignAsTable": false, "avg": true, "current": false, "max": false, "min": false, - "rightSide": true, + "rightSide": false, "show": true, "total": false, "values": true @@ -1583,7 +1841,7 @@ "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, - "stack": false, + "stack": true, "steppedLine": false, "targets": [ { @@ -1591,15 +1849,15 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (type)(libp2p_open_streams{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "expr": "sum by (instance)(libp2p_pubsub_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", "interval": "", - "legendFormat": "{{type}}", + "legendFormat": "{{instance}}", "refId": "A" } ], "thresholds": [], "timeRegions": [], - "title": "LibP2P Open Streams", + "title": "LibP2P PubSub Peers", "tooltip": { "shared": true, "sort": 2, @@ -1613,13 +1871,13 @@ }, "yaxes": [ { - "$$hashKey": "object:115", + "$$hashKey": "object:1232", "format": "short", "logBase": 1, "show": true }, { - "$$hashKey": "object:116", + "$$hashKey": "object:1233", "format": "short", "logBase": 1, "show": true @@ -1644,16 +1902,16 @@ }, "overrides": [] }, - "fill": 1, + "fill": 5, "fillGradient": 0, "gridPos": { "h": 6, "w": 12, - "x": 12, - "y": 56 + "x": 0, + "y": 78 }, "hiddenSeries": false, - "id": 7, + "id": 2, "legend": { "alignAsTable": false, "avg": true, @@ -1678,7 +1936,7 @@ "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, - "stack": false, + "stack": true, "steppedLine": false, "targets": [ { @@ -1686,42 +1944,15 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "libp2p_total_dial_attempts_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", - "format": "time_series", - "hide": false, + "expr": "sum by (instance)(libp2p_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", "interval": "", - "legendFormat": "Attempts: {{instance}}", + "legendFormat": "{{instance}}", "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "libp2p_failed_dials_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", - "hide": false, - "interval": "", - "legendFormat": "Failed: {{instance}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "libp2p_successful_dials_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", - "hide": false, - "interval": "", - "legendFormat": "Successful: {{instance}}", - "refId": "C" } ], "thresholds": [], "timeRegions": [], - "title": "LibP2P Dials", + "title": "LibP2P Peers", "tooltip": { "shared": true, "sort": 2, @@ -1735,13 +1966,13 @@ }, "yaxes": [ { - "$$hashKey": "object:189", + "$$hashKey": "object:1306", "format": "short", "logBase": 1, "show": true }, { - "$$hashKey": "object:190", + "$$hashKey": "object:1307", "format": "short", "logBase": 1, "show": true @@ -1752,51 +1983,150 @@ } }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 12, - "x": 0, - "y": 62 - }, - "hiddenSeries": false, - "id": 6, - "legend": { - "alignAsTable": false, - "avg": true, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "9.2.5", - "pointradius": 2, - "points": false, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 78 + }, + "id": 83, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.2.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "waku_peer_store_size{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "interval": "", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Peer Store Size", + "type": "timeseries" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 84 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "alignAsTable": false, + "avg": true, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, @@ -1808,15 +2138,15 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (instance)(process_open_fds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "expr": "sum by (type)(libp2p_open_streams{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", "interval": "", - "legendFormat": "{{instance}}", + "legendFormat": "{{type}}", "refId": "A" } ], "thresholds": [], "timeRegions": [], - "title": "Open File Descriptors", + "title": "LibP2P Open Streams", "tooltip": { "shared": true, "sort": 2, @@ -1830,13 +2160,13 @@ }, "yaxes": [ { - "$$hashKey": "object:511", + "$$hashKey": "object:115", "format": "short", "logBase": 1, "show": true }, { - "$$hashKey": "object:512", + "$$hashKey": "object:116", "format": "short", "logBase": 1, "show": true @@ -1857,8 +2187,7 @@ }, "fieldConfig": { "defaults": { - "links": [], - "unit": "percent" + "links": [] }, "overrides": [] }, @@ -1868,17 +2197,17 @@ "h": 6, "w": 12, "x": 12, - "y": 62 + "y": 84 }, "hiddenSeries": false, - "id": 10, + "id": 9, "legend": { - "alignAsTable": false, - "avg": false, + "alignAsTable": true, + "avg": true, "current": false, - "max": true, + "max": false, "min": false, - "rightSide": false, + "rightSide": true, "show": true, "total": false, "values": true @@ -1905,16 +2234,40 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "avg by (instance)(netdata_cpu_cpu_percentage_average{dimension=\"user\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "expr": "libp2p_pubsub_validation_success_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", "hide": false, "interval": "", - "legendFormat": "{{instance}}", + "legendFormat": "success {{instance}}", "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "libp2p_pubsub_validation_failure_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "hide": false, + "interval": "", + "legendFormat": "failure {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "libp2p_pubsub_validation_ignore_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "hide": false, + "interval": "", + "legendFormat": "ignore {{instance}}", + "refId": "C" } ], "thresholds": [], "timeRegions": [], - "title": "CPU Usage", + "title": "LibP2P Validations", "tooltip": { "shared": true, "sort": 2, @@ -1929,7 +2282,7 @@ "yaxes": [ { "$$hashKey": "object:189", - "format": "percent", + "format": "short", "logBase": 1, "show": true }, @@ -1945,239 +2298,30 @@ } }, { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "description": "", "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 4, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" + "links": [] }, "overrides": [] }, + "fill": 1, + "fillGradient": 0, "gridPos": { - "h": 8, + "h": 6, "w": 12, "x": 0, - "y": 68 - }, - "id": 44, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "nim_gc_mem_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "Nim total memory: {{instance}}", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "nim_gc_mem_occupied_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "hide": false, - "interval": "", - "legendFormat": "Nim occupied memory: {{instance}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "nim_gc_heap_instance_occupied_summed_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "hide": false, - "interval": "", - "legendFormat": "Nim total heap: {{instance}}", - "refId": "C" - } - ], - "title": "Nim Memory Usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 68 - }, - "id": 64, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "nim_gc_heap_instance_occupied_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{instance}} {{type_name}}", - "refId": "A" - } - ], - "title": "Heap allocation", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 12, - "x": 0, - "y": 76 + "y": 90 }, "hiddenSeries": false, - "id": 4, + "id": 6, "legend": { "alignAsTable": false, "avg": true, @@ -2210,7 +2354,7 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (instance)(process_virtual_memory_bytes{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "expr": "sum by (instance)(process_open_fds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", "interval": "", "legendFormat": "{{instance}}", "refId": "A" @@ -2218,7 +2362,7 @@ ], "thresholds": [], "timeRegions": [], - "title": "Virtual Memory", + "title": "Open File Descriptors", "tooltip": { "shared": true, "sort": 2, @@ -2232,13 +2376,13 @@ }, "yaxes": [ { - "$$hashKey": "object:263", - "format": "decbytes", + "$$hashKey": "object:511", + "format": "short", "logBase": 1, "show": true }, { - "$$hashKey": "object:264", + "$$hashKey": "object:512", "format": "short", "logBase": 1, "show": true @@ -2269,10 +2413,10 @@ "h": 6, "w": 12, "x": 12, - "y": 76 + "y": 90 }, "hiddenSeries": false, - "id": 5, + "id": 7, "legend": { "alignAsTable": false, "avg": true, @@ -2305,15 +2449,42 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum by (instance)(process_resident_memory_bytes{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "exemplar": true, + "expr": "libp2p_total_dial_attempts_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "format": "time_series", + "hide": false, "interval": "", - "legendFormat": "{{instance}}", + "legendFormat": "Attempts: {{instance}}", "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "libp2p_failed_dials_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "hide": false, + "interval": "", + "legendFormat": "Failed: {{instance}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "libp2p_successful_dials_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "hide": false, + "interval": "", + "legendFormat": "Successful: {{instance}}", + "refId": "C" } ], "thresholds": [], "timeRegions": [], - "title": "Resident Memory", + "title": "LibP2P Dials", "tooltip": { "shared": true, "sort": 2, @@ -2327,13 +2498,13 @@ }, "yaxes": [ { - "$$hashKey": "object:437", - "format": "decbytes", + "$$hashKey": "object:189", + "format": "short", "logBase": 1, "show": true }, { - "$$hashKey": "object:438", + "$$hashKey": "object:190", "format": "short", "logBase": 1, "show": true @@ -2343,37 +2514,12 @@ "align": false } }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 82 - }, - "id": 72, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "refId": "A" - } - ], - "title": "Bridge", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, + "description": "", "fieldConfig": { "defaults": { "color": { @@ -2386,7 +2532,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 3, + "fillOpacity": 4, "gradientMode": "none", "hideFrom": { "legend": false, @@ -2414,14 +2560,16 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "decbytes" }, "overrides": [] }, @@ -2429,9 +2577,9 @@ "h": 8, "w": 12, "x": 0, - "y": 83 + "y": 96 }, - "id": 70, + "id": 44, "options": { "legend": { "calcs": [], @@ -2451,9 +2599,9 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "increase(waku_bridge_transfers_total{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[10m])", + "expr": "nim_gc_mem_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", - "legendFormat": "{{fleet}} : {{type}}", + "legendFormat": "Nim total memory: {{instance}}", "refId": "A" }, { @@ -2462,10 +2610,10 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "increase(envelopes_valid_total{instance=~\"bridge*[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[10m])", + "expr": "nim_gc_mem_occupied_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "hide": false, "interval": "", - "legendFormat": "{{fleet}} : v1_envelopes", + "legendFormat": "Nim occupied memory: {{instance}}", "refId": "B" }, { @@ -2474,103 +2622,68 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "increase(waku_node_messages_total{instance=~\"bridge*[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[10m])", + "expr": "nim_gc_heap_instance_occupied_summed_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "hide": false, "interval": "", - "legendFormat": "{{fleet}} : v2_messages", + "legendFormat": "Nim total heap: {{instance}}", "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "increase(envelopes_dropped_total{instance=~\"bridge*[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[10m])", - "hide": false, - "interval": "", - "legendFormat": "{{fleet}} : v1_envelopes_dropped ({{reason}})", - "refId": "D" } ], - "title": "Bridge (10m rate)", + "title": "Nim Memory Usage", "type": "timeseries" }, { - "datasource": { - "type": "prometheus", + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } + "links": [], + "unit": "percent" }, "overrides": [] }, + "fill": 1, + "fillGradient": 0, "gridPos": { - "h": 8, + "h": 6, "w": 12, "x": 12, - "y": 83 + "y": 96 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true }, - "id": 74, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "alertThreshold": true }, + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, "targets": [ { "datasource": { @@ -2578,52 +2691,44 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "connected_peers{instance=~\"bridge*[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "v1_connected_peers", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "libp2p_pubsub_peers{instance=~\"bridge*[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "expr": "avg by (instance)(netdata_cpu_cpu_percentage_average{dimension=\"user\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", "hide": false, "interval": "", - "legendFormat": "v2_connected_peers", - "refId": "B" + "legendFormat": "{{instance}}", + "refId": "A" } ], - "title": "Connected Peers", - "type": "timeseries" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + "thresholds": [], + "timeRegions": [], + "title": "CPU Usage", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 91 + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] }, - "id": 34, - "panels": [], - "targets": [ + "yaxes": [ { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "refId": "A" + "$$hashKey": "object:189", + "format": "percent", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:190", + "format": "short", + "logBase": 1, + "show": true } ], - "title": "Store/Archive", - "type": "row" + "yaxis": { + "align": false + } }, { "datasource": { @@ -2670,24 +2775,26 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", "value": 80 } ] - } + }, + "unit": "decbytes" }, "overrides": [] }, "gridPos": { - "h": 6, + "h": 8, "w": 12, - "x": 0, - "y": 92 + "x": 12, + "y": 102 }, - "id": 36, + "id": 64, "options": { "legend": { "calcs": [], @@ -2707,120 +2814,217 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "waku_store_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "expr": "nim_gc_heap_instance_occupied_bytes{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", - "legendFormat": "{{instance}}", + "legendFormat": "{{instance}} {{type_name}}", "refId": "A" } ], - "title": "Waku Store Peers", + "title": "Heap allocation", "type": "timeseries" }, { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 2, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } + "links": [] }, "overrides": [] }, + "fill": 1, + "fillGradient": 0, "gridPos": { "h": 6, "w": 12, - "x": 12, - "y": 92 + "x": 0, + "y": 104 }, - "id": 38, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": true, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "alertThreshold": true }, - "targets": [ - { + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "editorMode": "code", - "exemplar": true, - "expr": "waku_store_messages{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "hide": false, + "expr": "sum by (instance)(process_virtual_memory_bytes{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", "interval": "", - "legendFormat": "{{type}}: {{instance}}", - "range": true, + "legendFormat": "{{instance}}", "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Virtual Memory", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:263", + "format": "decbytes", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:264", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "links": [] }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 110 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": true, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "editorMode": "code", - "expr": "waku_archive_messages{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "hide": false, - "legendFormat": "{{type}}: {{instance}}", - "range": true, - "refId": "B" + "expr": "sum by (instance)(process_resident_memory_bytes{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" } ], - "title": "Waku Archive Messages", - "type": "timeseries" + "thresholds": [], + "timeRegions": [], + "title": "Resident Memory", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:437", + "format": "decbytes", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:438", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 116 + }, + "id": 111, + "panels": [], + "title": "RLN", + "type": "row" }, { "datasource": { @@ -2839,7 +3043,7 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 3, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -2867,7 +3071,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -2879,12 +3084,12 @@ "overrides": [] }, "gridPos": { - "h": 6, - "w": 12, + "h": 8, + "w": 8, "x": 0, - "y": 98 + "y": 117 }, - "id": 62, + "id": 113, "options": { "legend": { "calcs": [], @@ -2903,14 +3108,15 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "increase(waku_store_queries{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m])", - "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" + "editorMode": "code", + "expr": "changes(waku_rln_proof_generation_duration_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m])", + "hide": false, + "legendFormat": "Proofs generated: {{instance}}", + "range": true, + "refId": "B" } ], - "title": "Waku Store Queries (1m rate)", + "title": "RLN proofs generated (1m rate)", "type": "timeseries" }, { @@ -2958,7 +3164,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -2970,12 +3177,12 @@ "overrides": [] }, "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 98 + "h": 8, + "w": 8, + "x": 8, + "y": 117 }, - "id": 40, + "id": 115, "options": { "legend": { "calcs": [], @@ -2995,11 +3202,8 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": true, - "expr": "sum by (type)(increase(waku_archive_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[1m]))", - "hide": false, - "interval": "", - "legendFormat": "{{type}}", + "expr": "waku_rln_invalid_messages_total_total{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "legendFormat": "{{type}} : {{instance}}", "range": true, "refId": "A" }, @@ -3009,101 +3213,92 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": true, - "expr": "sum by (type)(increase(waku_store_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[1m]))", + "expr": "waku_rln_spam_messages_total_total{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "hide": false, - "interval": "", - "legendFormat": "{{type}}", + "legendFormat": "spam : {{instance}}", "range": true, - "refId": "B" + "refId": "C" } ], - "title": "Waku Archive Errors (1m rate)", + "title": "RLN message counter", "type": "timeseries" }, { - "cards": {}, - "color": { - "cardColor": "#b4ff00", - "colorScale": "sqrt", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "mode": "spectrum" - }, - "dataFormat": "tsbuckets", "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { + "color": { + "mode": "palette-classic" + }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, "scaleDistribution": { "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] } }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 104 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 77, - "legend": { - "show": false + "h": 8, + "w": 8, + "x": 16, + "y": 117 }, - "maxDataPoints": 60, + "id": 117, "options": { - "calculate": false, - "calculation": {}, - "cellGap": 2, - "cellValues": {}, - "color": { - "exponent": 0.5, - "fill": "#b4ff00", - "mode": "scheme", - "reverse": false, - "scale": "exponential", - "scheme": "RdYlGn", - "steps": 128 - }, - "exemplars": { - "color": "rgba(255,0,255,0.7)" - }, - "filterValues": { - "le": 1e-9 - }, "legend": { - "show": false - }, - "rowsFrame": { - "layout": "auto" + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showValue": "never", "tooltip": { - "show": true, - "yHistogram": true - }, - "yAxis": { - "axisPlacement": "left", - "decimals": 0, - "reverse": false, - "unit": "s" + "mode": "single", + "sort": "none" } }, - "pluginVersion": "9.2.5", - "reverseYBuckets": false, "targets": [ { "datasource": { @@ -3111,127 +3306,92 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "expr": "sum(increase(waku_archive_query_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", - "format": "heatmap", - "hide": false, - "legendFormat": "{{le}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "editorMode": "code", - "expr": "sum(increase(waku_store_query_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", - "format": "heatmap", - "hide": true, - "legendFormat": "{{le}}", + "expr": "waku_rln_proof_verification_total_total{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "legendFormat": "{{instance}}", "range": true, "refId": "A" } ], - "title": "Waku Archive Query Duration", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "yAxis": { - "decimals": 0, - "format": "s", - "logBase": 1, - "show": true - }, - "yBucketBound": "auto" + "title": "RLN proofs verified", + "type": "timeseries" }, { - "cards": {}, - "color": { - "cardColor": "#b4ff00", - "colorScale": "sqrt", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "mode": "spectrum" - }, - "dataFormat": "tsbuckets", "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, "fieldConfig": { "defaults": { + "color": { + "mode": "palette-classic" + }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, "scaleDistribution": { "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } - } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 7, + "h": 8, "w": 12, - "x": 12, - "y": 104 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 75, - "legend": { - "show": false + "x": 0, + "y": 125 }, - "maxDataPoints": 60, + "id": 119, "options": { - "calculate": false, - "calculation": {}, - "cellGap": 2, - "cellValues": {}, - "color": { - "exponent": 0.5, - "fill": "#b4ff00", - "mode": "scheme", - "reverse": false, - "scale": "exponential", - "scheme": "RdYlGn", - "steps": 128 - }, - "exemplars": { - "color": "rgba(255,0,255,0.7)" - }, - "filterValues": { - "le": 1e-9 - }, "legend": { - "show": false - }, - "rowsFrame": { - "layout": "auto" + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showValue": "never", "tooltip": { - "show": true, - "yHistogram": true - }, - "yAxis": { - "axisPlacement": "left", - "decimals": 0, - "reverse": false, - "unit": "s" + "mode": "single", + "sort": "none" } }, - "pluginVersion": "9.2.5", - "reverseYBuckets": false, "targets": [ { "datasource": { @@ -3239,140 +3399,3561 @@ "uid": "P6693426190CB2316" }, "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(waku_archive_insert_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", - "format": "heatmap", - "hide": false, - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{le}}", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(waku_store_insert_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", - "format": "heatmap", - "hide": true, - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{le}}", + "expr": "waku_rln_proof_generation_duration_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "legendFormat": "{{instance}}", + "range": true, "refId": "A" } ], - "title": "Waku Archive Insert Duration", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "yAxis": { - "decimals": 0, - "format": "s", - "logBase": 1, - "show": true - }, - "yBucketBound": "auto" + "title": "RLN proof generation duration (seconds)", + "type": "timeseries" }, { - "collapsed": true, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 111 - }, - "id": 20, - "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" }, - "description": "Waku Filter Peers", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 125 + }, + "id": 121, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "waku_rln_proof_verification_duration_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "RLN proof verification duration (seconds)", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 133 + }, + "id": 34, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "refId": "A" + } + ], + "title": "Store/Archive", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 134 + }, + "id": 36, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "waku_store_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "title": "Waku Store Peers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 134 + }, + "id": 38, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "waku_store_messages{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "hide": false, + "interval": "", + "legendFormat": "{{type}}: {{instance}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "waku_archive_messages{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "hide": false, + "legendFormat": "{{type}}: {{instance}}", + "range": true, + "refId": "B" + } + ], + "title": "Waku Archive Messages", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 140 + }, + "id": 62, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "increase(waku_store_queries{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m])", + "interval": "", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Store Queries (1m rate)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 140 + }, + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum by (type)(increase(waku_archive_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "{{type}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum by (type)(increase(waku_store_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "{{type}}", + "range": true, + "refId": "B" + } + ], + "title": "Waku Archive Errors (1m rate)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 10, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "query_tag_ANALYZEmessages" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 146 + }, + "id": 144, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min", + "stdDev" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "query_time_secs{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", phase=\"sendQuery\"} and deriv(query_time_secs{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", phase=\"sendQuery\"}[1m]) != 0", + "interval": "", + "legendFormat": "{{query}}", + "range": true, + "refId": "A" + } + ], + "title": "Time Send Query To DB (sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "Shows the time spent while waiting for feedback from the database. That time includes the database query time plus the time spent waiting for the response from the database.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 10, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 146 + }, + "id": 145, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min", + "stdDev" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "query_time_secs{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", phase=\"waitFinish\"} and deriv(query_time_secs{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", phase=\"waitFinish\"}[45s]) != 0", + "hide": false, + "interval": "", + "legendFormat": "{{query}}", + "range": true, + "refId": "A" + } + ], + "title": "Wait Queries To Finish (sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 10, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 157 + }, + "id": 146, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min", + "stdDev" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "waku_legacy_store_time_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"} and deriv(waku_legacy_store_time_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[45s]) != 0", + "interval": "", + "legendFormat": "{{phase}}", + "range": true, + "refId": "A" + } + ], + "title": "Store V2 Times (sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 10, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 157 + }, + "id": 148, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min", + "stdDev" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "waku_store_time_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"} and deriv(waku_store_time_seconds{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[45s]) != 0", + "interval": "", + "legendFormat": "{{phase}}", + "range": true, + "refId": "A" + } + ], + "title": "Store V3 Times (sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 165 + }, + "id": 149, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(query_count_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", query!=\"InsertRow\"}[1m]) and rate(query_count_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", query!=\"InsertRowMessagesLookup\"}[1m]) ", + "interval": "", + "legendFormat": "{{query}}", + "range": true, + "refId": "A" + } + ], + "title": "Not-Insert Queries Rate (query/sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 3, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "InsertRow" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 165 + }, + "id": 147, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(query_count_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\", query=\"InsertRow\"}[5m])", + "interval": "", + "legendFormat": "{{query}}", + "range": true, + "refId": "A" + } + ], + "title": "Insert Queries Rate (insert/sec)", + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 173 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 77, + "legend": { + "show": false + }, + "maxDataPoints": 60, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": {}, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Turbo", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "9.2.5", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "sum(increase(waku_archive_query_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "sum(increase(waku_store_query_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": true, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Archive Query Duration", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 173 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 75, + "legend": { + "show": false + }, + "maxDataPoints": 60, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": {}, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Turbo", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "9.2.5", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(waku_archive_insert_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(waku_store_insert_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": true, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(waku_legacy_archive_insert_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "C" + } + ], + "title": "Waku Archive Insert Duration", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 0, + "y": 180 + }, + "id": 142, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=~\"/vac/waku/store/2.*\", direction=\"in\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v2 protocol traffic (in)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 12, + "y": 180 + }, + "id": 130, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/store-query/3.0.0\", direction=\"in\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v3 protocol traffic (in)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 0, + "y": 193 + }, + "id": 132, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=~\"/vac/waku/store/2.*\", direction=\"out\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v2 protocol traffic (out)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 12, + "y": 193 + }, + "id": 143, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/store-query/3.0.0\", direction=\"out\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v3 protocol traffic (out)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 0, + "y": 206 + }, + "id": 128, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{service =~\"/vac/waku/store/2.*\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} - {{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v2 query request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 12, + "y": 206 + }, + "id": 141, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{service =~\"/vac/waku/store-query/3.*\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} - {{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Store v3 query request rates", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 219 + }, + "id": 87, + "panels": [], + "title": "Filter", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 220 + }, + "id": 93, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "rate(waku_filter_requests{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{type}} : {{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Filter Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 220 + }, + "id": 89, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "waku_filter_subscriptions{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Filter Subscriptions", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 220 + }, + "id": 91, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "rate(waku_filter_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{type}} : {{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Filter Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 228 + }, + "id": 95, + "options": { + "calculate": false, + "cellGap": 2, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "RdYlGn", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "9.2.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "sum(increase(waku_filter_request_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Filter Request Duration", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 228 + }, + "id": 97, + "options": { + "calculate": false, + "cellGap": 2, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "RdYlGn", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "9.2.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "expr": "sum(increase(waku_filter_handle_message_duration_seconds_bucket{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}[$__rate_interval])) by (le)", + "format": "heatmap", + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Waku Filter Handle Message Duration", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "boot-01.ac-cn-hongkong-c.status.prod - rejected", + "boot-01.ac-cn-hongkong-c.status.prod - served" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true } + } + ] + } + ] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 0, + "y": 236 + }, + "id": 134, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{service = \"/vac/waku/filter-subscribe/2.0.0-beta1\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} - {{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Filter subscribe request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 12, + "y": 236 + }, + "id": 136, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/filter-push/2.0.0-beta1\", direction=\"out\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Filter protocol message push traffic (out)", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 249 + }, + "id": 28, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "refId": "A" + } + ], + "title": "Lightpush", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 250 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "waku_lightpush_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "interval": "", + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "title": "Waku Lightpush Peers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 250 + }, + "id": 32, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "exemplar": true, + "expr": "waku_lightpush_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "interval": "", + "legendFormat": "{{type}}: {[instance}}", + "refId": "A" + } + ], + "title": "Waku Lightpush Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 258 + }, + "id": 138, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{service = \"/vac/waku/lightpush/2.0.0-beta1\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} . {{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Lightpush request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 258 + }, + "id": 140, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/lightpush/2.0.0-beta1\", direction=\"in\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "interval": "", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Lightpush protocol traffic (in)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 270 + }, + "id": 151, + "panels": [], + "title": "Peer Exchange", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 271 + }, + "id": 153, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{service = \"/vac/waku/peer-exchange/2.0.0-alpha1\", instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} . {{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Peer Exchange request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 271 + }, + "id": 154, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_px_peers_sent_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Peer Exchange number of sent peers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 283 + }, + "id": 156, + "options": { + "displayMode": "basic", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true + }, + "pluginVersion": "9.2.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "builder", + "expr": "sum by(instance, type) (waku_px_errors_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"})", + "legendFormat": "{{instance}} - {{type}}", + "range": true, + "refId": "A" + } + ], + "title": "Peer Exchange errors total", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "overrides": [] + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 283 + }, + "id": 155, + "options": { + "legend": { + "calcs": [ + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_px_errors_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[$__rate_interval])", + "legendFormat": "{{instance}} - {{type}}", + "range": true, + "refId": "A" + } + ], + "title": "Peer Exchange errors", + "type": "timeseries" + }, + { + "collapsed": true, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 295 + }, + "id": 15, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" }, + "description": "number of swap peers", + "fill": 1, + "fillGradient": 0, "gridPos": { - "h": 8, + "h": 7, "w": 8, "x": 0, - "y": 103 + "y": 83 }, - "id": 22, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "alertThreshold": true }, + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, "targets": [ { "datasource": { @@ -3380,113 +6961,220 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "waku_filter_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "hide": false, + "expr": "waku_swap_peers_count{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", "legendFormat": "{{instance}}", "refId": "A" } ], - "title": "Waku Filter Peers", - "type": "timeseries" + "thresholds": [], + "timeRegions": [], + "title": "Waku Swap Peers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:139", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:140", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } }, { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 3, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, + "description": "swap account state for each peer", + "fill": 1, + "fillGradient": 0, "gridPos": { - "h": 8, + "h": 7, "w": 8, "x": 8, - "y": 103 + "y": 83 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": 150, + "total": false, + "values": false }, - "id": 24, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.2.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"250.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "250", + "refId": "A" }, - "tooltip": { - "mode": "single", - "sort": "none" + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "200", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "150", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "100", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"0.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "50", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"0.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "0", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "-50", + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "-100", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", + "interval": "", + "legendFormat": "-150", + "refId": "I" } + ], + "thresholds": [], + "timeRegions": [], + "title": "Waku Swap Account State", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" }, - "targets": [ + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "waku_filter_subscribers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" + "$$hashKey": "object:139", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:140", + "format": "short", + "logBase": 1, + "show": true } ], - "title": "Waku Filter Subscribers", - "type": "timeseries" + "yaxis": { + "align": false + } }, { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -3539,12 +7227,12 @@ "overrides": [] }, "gridPos": { - "h": 8, + "h": 7, "w": 8, "x": 16, - "y": 103 + "y": 83 }, - "id": 26, + "id": 42, "options": { "legend": { "calcs": [], @@ -3564,42 +7252,15 @@ "uid": "P6693426190CB2316" }, "exemplar": true, - "expr": "waku_filter_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", + "expr": "waku_swap_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", "interval": "", "legendFormat": "{{type}}: {{instance}}", "refId": "A" } ], - "title": "Waku Filter Errors", + "title": "Waku Swap Errors", "type": "timeseries" - } - ], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "refId": "A" - } - ], - "title": "Filter", - "type": "row" - }, - { - "collapsed": true, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 112 - }, - "id": 28, - "panels": [ + }, { "datasource": { "type": "prometheus", @@ -3611,6 +7272,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -3644,13 +7307,10 @@ "steps": [ { "color": "green" - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "none" }, "overrides": [] }, @@ -3658,14 +7318,14 @@ "h": 8, "w": 12, "x": 0, - "y": 96 + "y": 90 }, - "id": 30, + "id": 103, "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom", + "placement": "right", "showLegend": true }, "tooltip": { @@ -3673,20 +7333,24 @@ "sort": "none" } }, + "pluginVersion": "9.2.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "waku_lightpush_peers{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{instance}}", + "editorMode": "code", + "exemplar": false, + "expr": "(increase(waku_node_messages_total{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1m]))/60", + "format": "heatmap", + "instant": false, + "legendFormat": "{{fleet}}_{{datacenter}}", + "range": true, "refId": "A" } ], - "title": "Waku Lightpush Peers", + "title": "Messages/second", "type": "timeseries" }, { @@ -3700,6 +7364,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -3733,13 +7399,10 @@ "steps": [ { "color": "green" - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "deckbytes" }, "overrides": [] }, @@ -3747,324 +7410,265 @@ "h": 8, "w": 12, "x": 12, - "y": 96 + "y": 90 }, - "id": 32, + "id": 102, "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "waku_lightpush_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{type}}: {[instance}}", - "refId": "A" - } - ], - "title": "Waku Lightpush Errors", - "type": "timeseries" - } - ], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "refId": "A" - } - ], - "title": "Lightpush", - "type": "row" - }, - { - "collapsed": true, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 113 - }, - "id": 15, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "description": "number of swap peers", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 84 - }, - "hiddenSeries": false, - "id": 13, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.4.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "exemplar": true, - "expr": "waku_swap_peers_count{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Waku Swap Peers", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:139", - "format": "short", - "logBase": 1, - "show": true - }, - { - "$$hashKey": "object:140", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "description": "swap account state for each peer", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 84 - }, - "hiddenSeries": false, - "id": 18, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "sideWidth": 150, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "percentage": false, - "pluginVersion": "8.4.3", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "pluginVersion": "9.2.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"250.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "250", + "editorMode": "code", + "exemplar": false, + "expr": "waku_histogram_message_size_sum{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}/waku_histogram_message_size_count{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "format": "heatmap", + "instant": false, + "legendFormat": "{{fleet}}_{{datacenter}}", + "range": true, "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + } + ], + "title": "Average msg size (kBytes)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "200", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "150", - "refId": "C" + "unit": "percent" }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "100", - "refId": "D" + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 98 + }, + "id": 101, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, + "showUnfilled": true + }, + "pluginVersion": "9.2.5", + "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"0.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "50", - "refId": "E" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + "editorMode": "code", + "exemplar": false, + "expr": "rate(waku_histogram_message_size_bucket{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1h])/scalar(rate(waku_histogram_message_size_count{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}[1h]))*100", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Message distrubution %/kBytes (Last Hour)", + "transformations": [], + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"0.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "0", - "refId": "F" - }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-50.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "-50", - "refId": "G" + "unit": "none" }, - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-100.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "-100", - "refId": "H" + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 106 + }, + "id": 105, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, + "text": { + "titleSize": 15, + "valueSize": 15 + }, + "textMode": "auto" + }, + "pluginVersion": "9.2.5", + "targets": [ { "datasource": { "type": "prometheus", "uid": "P6693426190CB2316" }, - "expr": "sum(waku_peer_swap_account_balance_bucket{le=\"-150.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"} - ignoring(le) waku_peer_swap_account_balance_bucket{le=\"-200.0\", instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"})", - "interval": "", - "legendFormat": "-150", - "refId": "I" + "editorMode": "code", + "exemplar": false, + "expr": "waku_connected_peers{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "format": "heatmap", + "instant": false, + "legendFormat": "Direction:{{direction}} {{protocol}}", + "range": true, + "refId": "A" } ], - "thresholds": [], - "timeRegions": [], - "title": "Waku Swap Account State", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" + "title": "Connected Peers per Protocol", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] }, - "yaxes": [ - { - "$$hashKey": "object:139", - "format": "short", - "logBase": 1, - "show": true + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 106 + }, + "id": 104, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, + "text": { + "titleSize": 15, + "valueSize": 15 + }, + "textMode": "auto" + }, + "pluginVersion": "9.2.5", + "targets": [ { - "$$hashKey": "object:140", - "format": "short", - "logBase": 1, - "show": true + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "editorMode": "code", + "exemplar": false, + "expr": "waku_streams_peers{instance=~\"[[host]].([[dc:pipe]]).([[fleet:pipe]])\"}", + "format": "heatmap", + "instant": false, + "legendFormat": "Direction:{{direction}} {{protocol}}", + "range": true, + "refId": "A" } ], - "yaxis": { - "align": false - } - }, + "title": "Connected Streams per Protocol", + "type": "stat" + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P6693426190CB2316" + }, + "refId": "A" + } + ], + "title": "Swap", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 296 + }, + "id": 107, + "panels": [ { "datasource": { "type": "prometheus", @@ -4076,6 +7680,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -4115,17 +7721,18 @@ "value": 80 } ] - } + }, + "unit": "decgbytes" }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 8, - "x": 16, + "h": 8, + "w": 12, + "x": 0, "y": 84 }, - "id": 42, + "id": 109, "options": { "legend": { "calcs": [], @@ -4144,31 +7751,22 @@ "type": "prometheus", "uid": "P6693426190CB2316" }, - "exemplar": true, - "expr": "waku_swap_errors{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\"}", - "interval": "", - "legendFormat": "{{type}}: {{instance}}", + "editorMode": "builder", + "expr": "netdata_disk_space_GiB_average{instance=~\"[[host]].([[dc:pipe]]).*.([[fleet:pipe]])\",dimension=\"used\", family=\"/data\"}", + "legendFormat": "{{ instance }}", + "range": true, "refId": "A" } ], - "title": "Waku Swap Errors", + "title": "Disk space used", "type": "timeseries" } ], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "P6693426190CB2316" - }, - "refId": "A" - } - ], - "title": "Swap", + "title": "Disk", "type": "row" } ], - "refresh": "30s", + "refresh": false, "schemaVersion": 37, "style": "dark", "tags": [], @@ -4177,8 +7775,8 @@ { "current": { "selected": false, - "text": "node-.*", - "value": "node-.*" + "text": ".*", + "value": ".*" }, "hide": 0, "includeAll": false, @@ -4188,18 +7786,33 @@ "options": [ { "selected": true, + "text": ".*", + "value": ".*" + }, + { + "selected": false, "text": "node-.*", "value": "node-.*" + }, + { + "selected": false, + "text": "boot-.*", + "value": "boot-.*" + }, + { + "selected": false, + "text": "store-.*", + "value": "store-.*" } ], - "query": "node-.*", - "queryValue": "node-.*", + "query": ".*,node-.*,boot-.*,store-.*", + "queryValue": "", "skipUrlSync": false, "type": "custom" }, { "current": { - "selected": true, + "selected": false, "text": [ "status.prod" ], @@ -4223,7 +7836,7 @@ "refId": "StandardVariableQuery" }, "refresh": 1, - "regex": "/waku|status/", + "regex": "/waku|status|shards/", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", @@ -4233,7 +7846,7 @@ }, { "current": { - "selected": true, + "selected": false, "text": [ "All" ], @@ -4268,7 +7881,7 @@ ] }, "time": { - "from": "now-10d", + "from": "now-24h", "to": "now" }, "timepicker": { @@ -4287,6 +7900,6 @@ "timezone": "browser", "title": "Nim-Waku V2", "uid": "qrp_ZCTGz", - "version": 104, + "version": 154, "weekStart": "" } diff --git a/metrics/waku-single-node-dashboard.json b/metrics/waku-single-node-dashboard.json index 8e423a06f8..a3af53a7a6 100644 --- a/metrics/waku-single-node-dashboard.json +++ b/metrics/waku-single-node-dashboard.json @@ -1,53 +1,4 @@ { - "__inputs": [ - { - "name": "DS_NWAKU_PROMETHEUS", - "label": "Nwaku_Prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "panel", - "id": "gauge", - "name": "Gauge", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "9.1.5" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph (old)", - "version": "" - }, - { - "type": "panel", - "id": "heatmap", - "name": "Heatmap", - "version": "" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], "annotations": { "list": [ { @@ -74,7 +25,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": null, "links": [], "liveNow": false, "panels": [ @@ -157,6 +107,8 @@ }, "id": 52, "options": { + "minVizHeight": 75, + "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": [ @@ -167,9 +119,10 @@ }, "showThresholdLabels": false, "showThresholdMarkers": true, + "sizing": "auto", "text": {} }, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "targets": [ { "datasource": { @@ -200,6 +153,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -213,6 +167,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -297,6 +252,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -310,6 +266,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -445,6 +402,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -458,6 +416,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -567,6 +526,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -580,6 +540,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -693,7 +654,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -790,7 +751,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -887,7 +848,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -984,7 +945,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -1111,7 +1072,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -1208,7 +1169,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -1293,6 +1254,196 @@ "align": false } }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 78, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_relay_network_bytes_total{direction=\"in\"}[$__rate_interval])", + "legendFormat": "{{topic}} / {{type}}", + "range": true, + "refId": "A" + } + ], + "title": "Relay traffic per shard (in)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 79, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_relay_network_bytes_total{direction=\"out\"}[$__rate_interval])", + "legendFormat": "{{topic}}", + "range": true, + "refId": "A" + } + ], + "title": "Relay traffic per shard (out)", + "type": "timeseries" + }, { "datasource": { "type": "prometheus", @@ -1305,6 +1456,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1318,6 +1470,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1356,7 +1509,7 @@ "h": 8, "w": 12, "x": 0, - "y": 36 + "y": 45 }, "id": 44, "options": { @@ -1422,6 +1575,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1435,6 +1589,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1473,7 +1628,7 @@ "h": 8, "w": 12, "x": 12, - "y": 36 + "y": 45 }, "id": 64, "options": { @@ -1525,7 +1680,7 @@ "h": 6, "w": 12, "x": 0, - "y": 44 + "y": 53 }, "hiddenSeries": false, "id": 4, @@ -1547,7 +1702,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -1622,7 +1777,7 @@ "h": 6, "w": 12, "x": 12, - "y": 44 + "y": 53 }, "hiddenSeries": false, "id": 5, @@ -1644,7 +1799,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", @@ -1708,7 +1863,7 @@ "h": 1, "w": 24, "x": 0, - "y": 50 + "y": 59 }, "id": 34, "panels": [], @@ -1735,6 +1890,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1748,6 +1904,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1785,7 +1942,7 @@ "h": 6, "w": 12, "x": 0, - "y": 51 + "y": 60 }, "id": 36, "options": { @@ -1827,6 +1984,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1840,6 +1998,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1877,7 +2036,7 @@ "h": 6, "w": 12, "x": 12, - "y": 51 + "y": 60 }, "id": 38, "options": { @@ -1921,6 +2080,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1934,6 +2094,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1971,7 +2132,7 @@ "h": 6, "w": 12, "x": 0, - "y": 57 + "y": 66 }, "id": 62, "options": { @@ -2015,6 +2176,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2028,6 +2190,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2065,7 +2228,7 @@ "h": 6, "w": 12, "x": 12, - "y": 57 + "y": 66 }, "id": 40, "options": { @@ -2129,7 +2292,7 @@ "h": 7, "w": 12, "x": 0, - "y": 63 + "y": 72 }, "heatmap": {}, "hideZeroBuckets": true, @@ -2167,7 +2330,8 @@ }, "showValue": "never", "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -2177,7 +2341,7 @@ "unit": "s" } }, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "reverseYBuckets": false, "targets": [ { @@ -2243,7 +2407,7 @@ "h": 7, "w": 12, "x": 12, - "y": 63 + "y": 72 }, "heatmap": {}, "hideZeroBuckets": true, @@ -2281,7 +2445,8 @@ }, "showValue": "never", "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -2291,7 +2456,7 @@ "unit": "s" } }, - "pluginVersion": "9.1.5", + "pluginVersion": "10.4.2", "reverseYBuckets": false, "targets": [ { @@ -2325,44 +2490,18 @@ }, "yBucketBound": "auto" }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "8smvunn4k" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 70 - }, - "id": 20, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "8smvunn4k" - }, - "refId": "A" - } - ], - "title": "Filter", - "type": "row" - }, { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, - "description": "Waku Filter Peers", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2376,6 +2515,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2405,17 +2545,18 @@ "value": 80 } ] - } + }, + "unit": "reqps" }, "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 8, "x": 0, - "y": 71 + "y": 79 }, - "id": 22, + "id": 85, "options": { "legend": { "calcs": [], @@ -2432,23 +2573,22 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, - "exemplar": true, - "expr": "waku_filter_peers", - "hide": false, - "interval": "", - "legendFormat": "", + "editorMode": "code", + "expr": "rate(waku_service_requests_total{proto = \"/vac/waku/store-query/3.0.0\"}[$__rate_interval])", + "legendFormat": "{{state}}", + "range": true, "refId": "A" } ], - "title": "Waku Filter Peers", + "title": "Store query request rates", "type": "timeseries" }, { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, "fieldConfig": { "defaults": { @@ -2456,6 +2596,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2469,6 +2610,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2498,17 +2640,18 @@ "value": 80 } ] - } + }, + "unit": "binBps" }, "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 8, "x": 8, - "y": 71 + "y": 79 }, - "id": 24, + "id": 86, "options": { "legend": { "calcs": [], @@ -2525,30 +2668,30 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, - "exemplar": true, - "expr": "waku_filter_subscribers", - "interval": "", - "legendFormat": "", + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/store-query/3.0.0\", direction=\"in\"}[$__rate_interval])", + "legendFormat": "__auto", + "range": true, "refId": "A" } ], - "title": "Waku Filter Subscribers", + "title": "Store protocol traffic (in)", "type": "timeseries" }, { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, - "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2562,6 +2705,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2591,17 +2735,18 @@ "value": 80 } ] - } + }, + "unit": "binBps" }, "overrides": [] }, "gridPos": { - "h": 8, + "h": 10, "w": 8, "x": 16, - "y": 71 + "y": 79 }, - "id": 26, + "id": 87, "options": { "legend": { "calcs": [], @@ -2618,16 +2763,16 @@ { "datasource": { "type": "prometheus", - "uid": "${DS_NWAKU_PROMETHEUS}" + "uid": "PBFA97CFB590B2093" }, - "exemplar": true, - "expr": "waku_filter_errors", - "interval": "", - "legendFormat": "{{type}}: ", + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/store-query/3.0.0\", direction=\"out\"}[$__rate_interval])", + "legendFormat": "__auto", + "range": true, "refId": "A" } ], - "title": "Waku Filter Errors", + "title": "Store protocol traffic (out)", "type": "timeseries" }, { @@ -2640,9 +2785,9 @@ "h": 1, "w": 24, "x": 0, - "y": 79 + "y": 89 }, - "id": 28, + "id": 20, "panels": [], "targets": [ { @@ -2653,7 +2798,7 @@ "refId": "A" } ], - "title": "Lightpush", + "title": "Filter", "type": "row" }, { @@ -2661,12 +2806,14 @@ "type": "prometheus", "uid": "${DS_NWAKU_PROMETHEUS}" }, + "description": "Waku Filter Peers", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2680,6 +2827,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2715,11 +2863,11 @@ }, "gridPos": { "h": 8, - "w": 12, + "w": 8, "x": 0, - "y": 80 + "y": 90 }, - "id": 30, + "id": 22, "options": { "legend": { "calcs": [], @@ -2739,13 +2887,14 @@ "uid": "${DS_NWAKU_PROMETHEUS}" }, "exemplar": true, - "expr": "waku_lightpush_peers", + "expr": "waku_filter_peers", + "hide": false, "interval": "", "legendFormat": "", "refId": "A" } ], - "title": "Waku Lightpush Peers", + "title": "Waku Filter Peers", "type": "timeseries" }, { @@ -2759,6 +2908,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2772,6 +2922,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2807,11 +2958,11 @@ }, "gridPos": { "h": 8, - "w": 12, - "x": 12, - "y": 80 + "w": 8, + "x": 8, + "y": 90 }, - "id": 32, + "id": 24, "options": { "legend": { "calcs": [], @@ -2831,26 +2982,809 @@ "uid": "${DS_NWAKU_PROMETHEUS}" }, "exemplar": true, - "expr": "waku_lightpush_errors", + "expr": "waku_filter_subscribers", "interval": "", - "legendFormat": "{{type}}: {[instance}}", + "legendFormat": "", "refId": "A" } ], - "title": "Waku Lightpush Errors", + "title": "Waku Filter Subscribers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 90 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "exemplar": true, + "expr": "waku_filter_errors", + "interval": "", + "legendFormat": "{{type}}: ", + "refId": "A" + } + ], + "title": "Waku Filter Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 0, + "y": 98 + }, + "id": 80, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{proto = \"/vac/waku/filter-subscribe/2.0.0-beta1\"}[$__rate_interval])", + "legendFormat": "{{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Filter subscribe request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 8, + "y": 98 + }, + "id": 81, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/filter-subscribe/2.0.0-beta1\", direction=\"out\"}[$__rate_interval])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Filter protocol message push traffic (out)", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "8smvunn4k" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 108 + }, + "id": 28, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "8smvunn4k" + }, + "refId": "A" + } + ], + "title": "Lightpush", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 109 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "exemplar": true, + "expr": "waku_lightpush_peers", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Waku Lightpush Peers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 109 + }, + "id": 32, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_NWAKU_PROMETHEUS}" + }, + "exemplar": true, + "expr": "waku_lightpush_errors", + "interval": "", + "legendFormat": "{{type}}: {[instance}}", + "refId": "A" + } + ], + "title": "Waku Lightpush Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 0, + "y": 117 + }, + "id": 82, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_requests_total{proto = \"/vac/waku/lightpush/2.0.0-beta1\"}[$__rate_interval])", + "legendFormat": "{{state}}", + "range": true, + "refId": "A" + } + ], + "title": "Lightpush request rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 8, + "y": 117 + }, + "id": 84, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/lightpush/2.0.0-beta1\", direction=\"in\"}[$__rate_interval])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Lightpush protocol traffic (in)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 117 + }, + "id": 83, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(waku_service_network_bytes_total{service=\"/vac/waku/lightpush/2.0.0-beta1\", direction=\"out\"}[$__rate_interval])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Lightpush protocol traffic (out)", "type": "timeseries" } ], - "refresh": "30s", - "schemaVersion": 37, - "style": "dark", + "refresh": "", + "schemaVersion": 39, "tags": [], "templating": { "list": [] }, "time": { - "from": "now-6h", - "to": "now" + "from": "2024-07-15T09:24:51.060Z", + "to": "2024-07-21T20:25:57.556Z" }, "timepicker": { "refresh_intervals": [ @@ -2867,7 +3801,7 @@ }, "timezone": "browser", "title": "nwaku single node dashboard", - "uid": "qrp_ZCTGz", - "version": 10, + "uid": "bds5mtjv3hdz4a", + "version": 2, "weekStart": "" } \ No newline at end of file diff --git a/migrations/message_store_postgres/content_script_version_7.nim b/migrations/message_store_postgres/content_script_version_7.nim new file mode 100644 index 0000000000..01d7ad84e1 --- /dev/null +++ b/migrations/message_store_postgres/content_script_version_7.nim @@ -0,0 +1,30 @@ +const ContentScriptVersion_7* = + """ + +-- Create lookup table +CREATE TABLE IF NOT EXISTS messages_lookup ( + timestamp BIGINT NOT NULL, + messageHash VARCHAR NOT NULL + ); + +-- Put data into lookup table +INSERT INTO messages_lookup (messageHash, timestamp) SELECT messageHash, timestamp from messages; + +ALTER TABLE messages_lookup ADD CONSTRAINT messageIndexLookupTable PRIMARY KEY (messageHash, timestamp); + +-- Create indexes +CREATE INDEX IF NOT EXISTS idx_messages_messagehash ON messages (messagehash); +CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages (timestamp); +CREATE INDEX IF NOT EXISTS idx_messages_lookup_messagehash ON messages_lookup (messagehash); +CREATE INDEX IF NOT EXISTS idx_messages_lookup_timestamp ON messages_lookup (timestamp); + +DROP INDEX IF EXISTS i_query_storedat; +DROP INDEX IF EXISTS i_query; + +CREATE INDEX IF NOT EXISTS idx_query_pubsubtopic ON messages (pubsubTopic); +CREATE INDEX IF NOT EXISTS idx_query_contenttopic ON messages (contentTopic); + +-- Update to new version +UPDATE version SET version = 7 WHERE version = 6; + +""" diff --git a/migrations/message_store_postgres/pg_migration_manager.nim b/migrations/message_store_postgres/pg_migration_manager.nim index e90a51fc24..051ac9e79a 100644 --- a/migrations/message_store_postgres/pg_migration_manager.nim +++ b/migrations/message_store_postgres/pg_migration_manager.nim @@ -1,6 +1,7 @@ import content_script_version_1, content_script_version_2, content_script_version_3, - content_script_version_4, content_script_version_5, content_script_version_6 + content_script_version_4, content_script_version_5, content_script_version_6, + content_script_version_7 type MigrationScript* = object version*: int @@ -17,6 +18,7 @@ const PgMigrationScripts* = MigrationScript(version: 4, scriptContent: ContentScriptVersion_4), MigrationScript(version: 5, scriptContent: ContentScriptVersion_5), MigrationScript(version: 6, scriptContent: ContentScriptVersion_6), + MigrationScript(version: 7, scriptContent: ContentScriptVersion_7), ] proc getMigrationScripts*(currentVersion: int64, targetVersion: int64): seq[string] = diff --git a/migrations/sent_msgs/00001_addNotDeliveredMessagesTable.up.sql b/migrations/sent_msgs/00001_addNotDeliveredMessagesTable.up.sql new file mode 100644 index 0000000000..2c0a13b485 --- /dev/null +++ b/migrations/sent_msgs/00001_addNotDeliveredMessagesTable.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS NotDeliveredMessages( + messageHash BLOB PRIMARY KEY, + timestamp INTEGER NOT NULL, + contentTopic BLOB NOT NULL, + pubsubTopic BLOB NOT NULL, + payload BLOB, + meta BLOB, + version INTEGER NOT NULL + ); \ No newline at end of file diff --git a/scripts/git_pre_commit_format.sh b/scripts/git_pre_commit_format.sh new file mode 100644 index 0000000000..f52c365070 --- /dev/null +++ b/scripts/git_pre_commit_format.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "Running pre-commit hook" + +# Regexp for grep to only choose some file extensions for formatting +exts="\.\(nim\|nims\)$" + +# Build nph lazily +make build-nph || (1>&2 echo "failed to build nph. Pre-commit formatting will not be done."; exit 0) + +# Format staged files +git diff --cached --name-only --diff-filter=ACMR | grep "$exts" | while read file; do + echo "Formatting $file" + make nph/"$file" + git add "$file" +done diff --git a/tests/common/test_all.nim b/tests/common/test_all.nim index 1afef23a90..7756f23adf 100644 --- a/tests/common/test_all.nim +++ b/tests/common/test_all.nim @@ -8,4 +8,5 @@ import ./test_parse_size, ./test_tokenbucket, ./test_requestratelimiter, + ./test_ratelimit_setting, ./test_timed_map diff --git a/tests/common/test_ratelimit_setting.nim b/tests/common/test_ratelimit_setting.nim new file mode 100644 index 0000000000..6f6ac8d38e --- /dev/null +++ b/tests/common/test_ratelimit_setting.nim @@ -0,0 +1,165 @@ +# Chronos Test Suite +# (c) Copyright 2022-Present +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +{.used.} + +import testutils/unittests +import chronos, libp2p/stream/connection +import std/[sequtils, options, tables] + +import ../../waku/common/rate_limit/request_limiter +import ../../waku/common/rate_limit/timed_map + +let proto = "ProtocolDescriptor" + +let conn1 = Connection(peerId: PeerId.random().tryGet()) +let conn2 = Connection(peerId: PeerId.random().tryGet()) +let conn3 = Connection(peerId: PeerId.random().tryGet()) + +suite "RateLimitSetting": + test "Parse rate limit setting - ok": + let test1 = "10/2m" + let test2 = " store : 10 /1h" + let test2a = "storev2 : 10 /1h" + let test2b = "storeV3: 12 /1s" + let test3 = "LIGHTPUSH: 10/ 1m" + let test4 = "px:10/2 s " + let test5 = "filter:42/66ms" + + let expU = UnlimitedRateLimit + let exp1: RateLimitSetting = (10, 2.minutes) + let exp2: RateLimitSetting = (10, 1.hours) + let exp2a: RateLimitSetting = (10, 1.hours) + let exp2b: RateLimitSetting = (12, 1.seconds) + let exp3: RateLimitSetting = (10, 1.minutes) + let exp4: RateLimitSetting = (10, 2.seconds) + let exp5: RateLimitSetting = (42, 66.milliseconds) + + let res1 = ProtocolRateLimitSettings.parse(@[test1]) + let res2 = ProtocolRateLimitSettings.parse(@[test2]) + let res2a = ProtocolRateLimitSettings.parse(@[test2a]) + let res2b = ProtocolRateLimitSettings.parse(@[test2b]) + let res3 = ProtocolRateLimitSettings.parse(@[test3]) + let res4 = ProtocolRateLimitSettings.parse(@[test4]) + let res5 = ProtocolRateLimitSettings.parse(@[test5]) + + check: + res1.isOk() + res1.get() == {GLOBAL: exp1, FILTER: FilterDefaultPerPeerRateLimit}.toTable() + res2.isOk() + res2.get() == + { + GLOBAL: expU, + FILTER: FilterDefaultPerPeerRateLimit, + STOREV2: exp2, + STOREV3: exp2, + }.toTable() + res2a.isOk() + res2a.get() == + {GLOBAL: expU, FILTER: FilterDefaultPerPeerRateLimit, STOREV2: exp2a}.toTable() + res2b.isOk() + res2b.get() == + {GLOBAL: expU, FILTER: FilterDefaultPerPeerRateLimit, STOREV3: exp2b}.toTable() + res3.isOk() + res3.get() == + {GLOBAL: expU, FILTER: FilterDefaultPerPeerRateLimit, LIGHTPUSH: exp3}.toTable() + res4.isOk() + res4.get() == + {GLOBAL: expU, FILTER: FilterDefaultPerPeerRateLimit, PEEREXCHG: exp4}.toTable() + res5.isOk() + res5.get() == {GLOBAL: expU, FILTER: exp5}.toTable() + + test "Parse rate limit setting - err": + let test1 = "10/2d" + let test2 = " stre : 10 /1h" + let test2a = "storev2 10 /1h" + let test2b = "storev3: 12 1s" + let test3 = "somethingelse: 10/ 1m" + let test4 = ":px:10/2 s " + let test5 = "filter:p42/66ms" + + let res1 = ProtocolRateLimitSettings.parse(@[test1]) + let res2 = ProtocolRateLimitSettings.parse(@[test2]) + let res2a = ProtocolRateLimitSettings.parse(@[test2a]) + let res2b = ProtocolRateLimitSettings.parse(@[test2b]) + let res3 = ProtocolRateLimitSettings.parse(@[test3]) + let res4 = ProtocolRateLimitSettings.parse(@[test4]) + let res5 = ProtocolRateLimitSettings.parse(@[test5]) + + check: + res1.isErr() + res2.isErr() + res2a.isErr() + res2b.isErr() + res3.isErr() + res4.isErr() + res5.isErr() + + test "Parse rate limit setting - complex": + let expU = UnlimitedRateLimit + + let test1 = @["lightpush:2/2ms", "10/2m", " store: 3/3s", " storev2:12/12s"] + let exp1 = { + GLOBAL: (10, 2.minutes), + FILTER: FilterDefaultPerPeerRateLimit, + LIGHTPUSH: (2, 2.milliseconds), + STOREV3: (3, 3.seconds), + STOREV2: (12, 12.seconds), + }.toTable() + + let res1 = ProtocolRateLimitSettings.parse(test1) + + check: + res1.isOk() + res1.get() == exp1 + res1.get().getSetting(PEEREXCHG) == (10, 2.minutes) + res1.get().getSetting(STOREV2) == (12, 12.seconds) + res1.get().getSetting(STOREV3) == (3, 3.seconds) + res1.get().getSetting(LIGHTPUSH) == (2, 2.milliseconds) + + let test2 = @["lightpush:2/2ms", " store: 3/3s", "px:10/10h", "filter:4/42ms"] + let exp2 = { + GLOBAL: expU, + LIGHTPUSH: (2, 2.milliseconds), + STOREV3: (3, 3.seconds), + STOREV2: (3, 3.seconds), + FILTER: (4, 42.milliseconds), + PEEREXCHG: (10, 10.hours), + }.toTable() + + let res2 = ProtocolRateLimitSettings.parse(test2) + + check: + res2.isOk() + res2.get() == exp2 + + let test3 = + @["storev2:1/1s", "store:3/3s", "storev3:4/42ms", "storev3:5/5s", "storev3:6/6s"] + let exp3 = { + GLOBAL: expU, + FILTER: FilterDefaultPerPeerRateLimit, + STOREV3: (6, 6.seconds), + STOREV2: (1, 1.seconds), + }.toTable() + + let res3 = ProtocolRateLimitSettings.parse(test3) + + check: + res3.isOk() + res3.get() == exp3 + res3.get().getSetting(LIGHTPUSH) == expU + + let test4 = newSeq[string](0) + let exp4 = {GLOBAL: expU, FILTER: FilterDefaultPerPeerRateLimit}.toTable() + + let res4 = ProtocolRateLimitSettings.parse(test4) + + check: + res4.isOk() + res4.get() == exp4 + res3.get().getSetting(LIGHTPUSH) == expU diff --git a/tests/common/test_requestratelimiter.nim b/tests/common/test_requestratelimiter.nim index 256e48118c..0b494c1bec 100644 --- a/tests/common/test_requestratelimiter.nim +++ b/tests/common/test_requestratelimiter.nim @@ -82,3 +82,17 @@ suite "RequestRateLimiter": # requests of other peers can also go check limiter.checkUsage(proto, conn2, now + 4100.milliseconds) == true check limiter.checkUsage(proto, conn3, now + 5.minutes) == true + + test "RequestRateLimiter lowest possible volume": + # keep limits low for easier calculation of ratios + let rateLimit: RateLimitSetting = (1, 1.seconds) + var limiter = newRequestRateLimiter(some(rateLimit)) + + let now = Moment.now() + # with first use we register the peer also and start its timer + check limiter.checkUsage(proto, conn1, now + 500.milliseconds) == true + + # run out of main tokens but still used one more token from the peer's bucket + check limiter.checkUsage(proto, conn1, now + 800.milliseconds) == false + check limiter.checkUsage(proto, conn1, now + 1499.milliseconds) == false + check limiter.checkUsage(proto, conn1, now + 1501.milliseconds) == true diff --git a/tests/node/peer_manager/test_peer_manager.nim b/tests/node/peer_manager/test_peer_manager.nim index 3d0cb08c2c..57acf13df4 100644 --- a/tests/node/peer_manager/test_peer_manager.nim +++ b/tests/node/peer_manager/test_peer_manager.nim @@ -20,8 +20,6 @@ suite "Peer Manager": serverKey {.threadvar.}: PrivateKey clientKey {.threadvar.}: PrivateKey clusterId {.threadvar.}: uint64 - shardTopic0 {.threadvar.}: string - shardTopic1 {.threadvar.}: string asyncSetup: listenPort = Port(0) @@ -29,17 +27,15 @@ suite "Peer Manager": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() clusterId = 1 - shardTopic0 = "/waku/2/rs/" & $clusterId & "/0" - shardTopic1 = "/waku/2/rs/" & $clusterId & "/1" asyncTest "light client is not disconnected": # Given two nodes with the same shardId let server = newTestWakuNode( - serverKey, listenAddress, listenPort, pubsubTopics = @[shardTopic0] + serverKey, listenAddress, listenPort, clusterId = clusterId, shards = @[0] ) client = newTestWakuNode( - clientKey, listenAddress, listenPort, pubsubTopics = @[shardTopic1] + clientKey, listenAddress, listenPort, clusterId = clusterId, shards = @[1] ) # And both mount metadata and filter @@ -71,10 +67,10 @@ suite "Peer Manager": # Given two nodes with the same shardId let server = newTestWakuNode( - serverKey, listenAddress, listenPort, pubsubTopics = @[shardTopic0] + serverKey, listenAddress, listenPort, clusterId = clusterId, shards = @[0] ) client = newTestWakuNode( - clientKey, listenAddress, listenPort, pubsubTopics = @[shardTopic0] + clientKey, listenAddress, listenPort, clusterId = clusterId, shards = @[1] ) # And both mount metadata and relay @@ -104,10 +100,10 @@ suite "Peer Manager": # Given two nodes with different shardIds let server = newTestWakuNode( - serverKey, listenAddress, listenPort, pubsubTopics = @[shardTopic0] + serverKey, listenAddress, listenPort, clusterId = clusterId, shards = @[0] ) client = newTestWakuNode( - clientKey, listenAddress, listenPort, pubsubTopics = @[shardTopic1] + clientKey, listenAddress, listenPort, clusterId = clusterId, shards = @[1] ) # And both mount metadata and relay diff --git a/tests/node/test_wakunode_lightpush.nim b/tests/node/test_wakunode_lightpush.nim index fba46e212c..30158ebd12 100644 --- a/tests/node/test_wakunode_lightpush.nim +++ b/tests/node/test_wakunode_lightpush.nim @@ -88,11 +88,9 @@ suite "Waku Lightpush - End To End": # Then the message is not relayed but not due to RLN assert publishResponse.isErr(), "We expect an error response" - assert ( - publishResponse.error.contains( - "Lightpush request has not been published to any peers" - ) - ), "incorrect error response" + + assert (publishResponse.error == protocol_metrics.notPublishedAnyPeer), + "incorrect error response" suite "Waku LightPush Validation Tests": asyncTest "Validate message size exceeds limit": @@ -181,8 +179,5 @@ suite "RLN Proofs as a Lightpush Service": # Then the message is not relayed but not due to RLN assert publishResponse.isErr(), "We expect an error response" - assert ( - publishResponse.error.contains( - "Lightpush request has not been published to any peers" - ) - ), "incorrect error response" + assert (publishResponse.error == protocol_metrics.notPublishedAnyPeer), + "incorrect error response" diff --git a/tests/node/test_wakunode_peer_exchange.nim b/tests/node/test_wakunode_peer_exchange.nim index c2a235045e..49f61d2959 100644 --- a/tests/node/test_wakunode_peer_exchange.nim +++ b/tests/node/test_wakunode_peer_exchange.nim @@ -84,7 +84,8 @@ suite "Waku Peer Exchange": # Then no peers are fetched check: node.peerManager.peerStore.peers.len == 0 - res.error == "PeerExchange is not mounted" + res.error.status_code == SERVICE_UNAVAILABLE + res.error.status_desc == some("PeerExchange is not mounted") asyncTest "Node fetches with mounted peer exchange, but no peers": # Given a node with peer exchange mounted @@ -92,7 +93,9 @@ suite "Waku Peer Exchange": # When a node fetches peers let res = await node.fetchPeerExchangePeers(1) - check res.error == "Peer exchange failure: peer_not_found_failure" + check: + res.error.status_code == SERVICE_UNAVAILABLE + res.error.status_desc == some("peer_not_found_failure") # Then no peers are fetched check node.peerManager.peerStore.peers.len == 0 diff --git a/tests/node/test_wakunode_peer_manager.nim b/tests/node/test_wakunode_peer_manager.nim index 4e10556f73..104baa6ef4 100644 --- a/tests/node/test_wakunode_peer_manager.nim +++ b/tests/node/test_wakunode_peer_manager.nim @@ -8,6 +8,7 @@ import chronos, # chronos/timer, chronicles, + times, libp2p/[peerstore, crypto/crypto, multiaddress] from times import getTime, toUnix @@ -62,9 +63,9 @@ suite "Peer Manager": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, listenIp, listenPort) + server = newTestWakuNode(serverKey, listenIp, Port(3000)) serverPeerStore = server.peerManager.peerStore - client = newTestWakuNode(clientKey, listenIp, listenPort) + client = newTestWakuNode(clientKey, listenIp, Port(3001)) clientPeerStore = client.peerManager.peerStore await allFutures(server.start(), client.start()) @@ -577,77 +578,49 @@ suite "Peer Manager": Connectedness.CannotConnect suite "Automatic Reconnection": - xasyncTest "Automatic Reconnection Implementation": + asyncTest "Automatic Reconnection Implementation": # Given two correctly initialised nodes, that are available for reconnection await server.mountRelay() await client.mountRelay() await client.connectToNodes(@[serverRemotePeerInfo]) - await server.switch.stop() - await client.switch.stop() - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect - serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect + + waitActive: + clientPeerStore.get(serverPeerId).connectedness == Connectedness.Connected and + serverPeerStore.get(clientPeerId).connectedness == Connectedness.Connected + + await client.disconnectNode(serverRemotePeerInfo) + + waitActive: + clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect and + serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect # When triggering the reconnection await client.peerManager.reconnectPeers(WakuRelayCodec) # Then both peers should be marked as Connected - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.Connected - serverPeerStore.get(clientPeerId).connectedness == Connectedness.Connected + waitActive: + clientPeerStore.get(serverPeerId).connectedness == Connectedness.Connected and + serverPeerStore.get(clientPeerId).connectedness == Connectedness.Connected - xasyncTest "Automatic Reconnection Implementation (With Backoff)": - # Given two correctly initialised nodes, that are available for reconnection - await server.mountRelay() - await client.mountRelay() - await client.connectToNodes(@[serverRemotePeerInfo]) - waitFor allFutures(server.switch.stop(), client.switch.stop()) - waitFor allFutures(server.switch.start(), client.switch.start()) - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect - serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect + ## Now let's do the same but with backoff period + await client.disconnectNode(serverRemotePeerInfo) - # When triggering a reconnection with a backoff period - let - backoffPeriod = 10.seconds - halfBackoffPeriod = 5.seconds + waitActive: + clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect and + serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect + # When triggering a reconnection with a backoff period + let backoffPeriod = chronos.seconds(1) + let beforeReconnect = getTime().toUnixFloat() await client.peerManager.reconnectPeers(WakuRelayCodec, backoffPeriod) - await sleepAsync(halfBackoffPeriod) - - # If the backoff period is not over, then the peers should still be marked as CanConnect - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect - serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect - - # When waiting for the backoff period to be over - await sleepAsync(halfBackoffPeriod) - - # Then both peers should be marked as Connected - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.Connected - serverPeerStore.get(clientPeerId).connectedness == Connectedness.Connected - - xasyncTest "Automatic Reconnection Implementation (After client restart)": - # Given two correctly initialised nodes, that are available for reconnection - await server.mountRelay() - await client.mountRelay() - await client.connectToNodes(@[serverRemotePeerInfo]) - await server.switch.stop() - await client.switch.stop() - check: - clientPeerStore.get(serverPeerId).connectedness == Connectedness.CanConnect - serverPeerStore.get(clientPeerId).connectedness == Connectedness.CanConnect - - # When triggering the reconnection, and some time for the reconnection to happen - waitFor allFutures(client.stop(), server.stop()) - await allFutures(server.start(), client.start()) - await sleepAsync(FUTURE_TIMEOUT_LONG) + let reconnectDurationWithBackoffPeriod = + getTime().toUnixFloat() - beforeReconnect # Then both peers should be marked as Connected check: clientPeerStore.get(serverPeerId).connectedness == Connectedness.Connected serverPeerStore.get(clientPeerId).connectedness == Connectedness.Connected + reconnectDurationWithBackoffPeriod > backoffPeriod.seconds.float suite "Handling Connections on Different Networks": # TODO: Implement after discv5 and peer manager's interaction is understood diff --git a/tests/node/test_wakunode_relay_rln.nim b/tests/node/test_wakunode_relay_rln.nim index e1bdb7c6a7..1304575c7d 100644 --- a/tests/node/test_wakunode_relay_rln.nim +++ b/tests/node/test_wakunode_relay_rln.nim @@ -96,9 +96,9 @@ proc getWakuRlnConfigOnChain*( ) proc setupRelayWithOnChainRln*( - node: WakuNode, pubsubTopics: seq[string], wakuRlnConfig: WakuRlnConfig + node: WakuNode, shards: seq[RelayShard], wakuRlnConfig: WakuRlnConfig ) {.async.} = - await node.mountRelay(pubsubTopics) + await node.mountRelay(shards) await node.mountRlnRelay(wakuRlnConfig) suite "Waku RlnRelay - End to End - Static": @@ -223,7 +223,7 @@ suite "Waku RlnRelay - End to End - Static": nodekey = generateSecp256k1Key() node = newTestWakuNode(nodekey, parseIpAddress("0.0.0.0"), Port(0)) - await node.mountRelay(@[DefaultPubsubTopic]) + await node.mountRelay(@[DefaultRelayShard]) let contractAddress = await uploadRLNContract(EthClient) let wakuRlnConfig = WakuRlnConfig( diff --git a/tests/node/test_wakunode_sharding.nim b/tests/node/test_wakunode_sharding.nim index e66e9e7b48..95b4043d99 100644 --- a/tests/node/test_wakunode_sharding.nim +++ b/tests/node/test_wakunode_sharding.nim @@ -186,11 +186,11 @@ suite "Sharding": # Given a connected server and client subscribed to different content topics let contentTopic1 = "/toychat/2/huilong/proto" - pubsubTopic1 = "/waku/2/rs/0/58355" - pubsubTopic12 = NsPubsubTopic.parse(contentTopic1) + shard1 = "/waku/2/rs/0/58355" + shard12 = RelayShard.parse(contentTopic1) # Automatically generated from the contentTopic above contentTopic2 = "/0/toychat2/2/huilong/proto" - pubsubTopic2 = "/waku/2/rs/0/23286" + shard2 = "/waku/2/rs/0/23286" # Automatically generated from the contentTopic above let @@ -201,7 +201,7 @@ suite "Sharding": # When the server publishes a message in the server's subscribed topic discard await server.publish( - some(pubsubTopic1), + some(shard1), WakuMessage(payload: "message1".toBytes(), contentTopic: contentTopic1), ) let @@ -216,7 +216,7 @@ suite "Sharding": serverHandler.reset() clientHandler.reset() discard await client.publish( - some(pubsubTopic2), + some(shard2), WakuMessage(payload: "message2".toBytes(), contentTopic: contentTopic2), ) let diff --git a/tests/node/test_wakunode_sync.nim b/tests/node/test_wakunode_sync.nim new file mode 100644 index 0000000000..0ffb3a8a61 --- /dev/null +++ b/tests/node/test_wakunode_sync.nim @@ -0,0 +1,188 @@ +{.used.} + +import std/net, testutils/unittests, chronos, libp2p/crypto/crypto + +import + ../../waku/ + [node/waku_node, node/peer_manager, waku_core, waku_store, waku_archive, waku_sync], + ../waku_store/store_utils, + ../waku_archive/archive_utils, + ../testlib/[wakucore, wakunode, testasync] + +suite "Store Sync - End to End": + var server {.threadvar.}: WakuNode + var client {.threadvar.}: WakuNode + + asyncSetup: + let timeOrigin = now() + + let messages = + @[ + fakeWakuMessage(@[byte 00], ts = ts(-90, timeOrigin)), + fakeWakuMessage(@[byte 01], ts = ts(-80, timeOrigin)), + fakeWakuMessage(@[byte 02], ts = ts(-70, timeOrigin)), + fakeWakuMessage(@[byte 03], ts = ts(-60, timeOrigin)), + fakeWakuMessage(@[byte 04], ts = ts(-50, timeOrigin)), + fakeWakuMessage(@[byte 05], ts = ts(-40, timeOrigin)), + fakeWakuMessage(@[byte 06], ts = ts(-30, timeOrigin)), + fakeWakuMessage(@[byte 07], ts = ts(-20, timeOrigin)), + fakeWakuMessage(@[byte 08], ts = ts(-10, timeOrigin)), + fakeWakuMessage(@[byte 09], ts = ts(00, timeOrigin)), + ] + + let + serverKey = generateSecp256k1Key() + clientKey = generateSecp256k1Key() + + server = newTestWakuNode(serverKey, IPv4_any(), Port(0)) + client = newTestWakuNode(clientKey, IPv4_any(), Port(0)) + + let serverArchiveDriver = newArchiveDriverWithMessages(DefaultPubsubTopic, messages) + let clientArchiveDriver = newArchiveDriverWithMessages(DefaultPubsubTopic, messages) + + let mountServerArchiveRes = server.mountArchive(serverArchiveDriver) + let mountClientArchiveRes = client.mountArchive(clientArchiveDriver) + + assert mountServerArchiveRes.isOk() + assert mountClientArchiveRes.isOk() + + await server.mountStore() + await client.mountStore() + + client.mountStoreClient() + server.mountStoreClient() + + let mountServerSync = await server.mountWakuSync( + maxFrameSize = 0, syncInterval = 1.hours, relayJitter = 0.seconds + ) + let mountClientSync = await client.mountWakuSync( + maxFrameSize = 0, syncInterval = 2.milliseconds, relayJitter = 0.seconds + ) + + assert mountServerSync.isOk(), mountServerSync.error + assert mountClientSync.isOk(), mountClientSync.error + + # messages are retreived when mounting Waku sync + # but based on interval so this is needed for client only + for msg in messages: + client.wakuSync.messageIngress(DefaultPubsubTopic, msg) + + await allFutures(server.start(), client.start()) + + let serverRemotePeerInfo = server.peerInfo.toRemotePeerInfo() + let clientRemotePeerInfo = client.peerInfo.toRemotePeerInfo() + + client.peerManager.addServicePeer(serverRemotePeerInfo, WakuSyncCodec) + server.peerManager.addServicePeer(clientRemotePeerInfo, WakuSyncCodec) + + client.peerManager.addServicePeer(serverRemotePeerInfo, WakuStoreCodec) + server.peerManager.addServicePeer(clientRemotePeerInfo, WakuStoreCodec) + + asyncTeardown: + # prevent premature channel shutdown + await sleepAsync(10.milliseconds) + + await allFutures(client.stop(), server.stop()) + + asyncTest "no message set differences": + check: + client.wakuSync.storageSize() == server.wakuSync.storageSize() + + await sleepAsync(10.milliseconds) + + check: + client.wakuSync.storageSize() == server.wakuSync.storageSize() + + asyncTest "client message set differences": + let msg = fakeWakuMessage(@[byte 10]) + + client.wakuSync.messageIngress(DefaultPubsubTopic, msg) + await client.wakuArchive.handleMessage(DefaultPubsubTopic, msg) + + check: + client.wakuSync.storageSize() != server.wakuSync.storageSize() + + await sleepAsync(10.milliseconds) + + check: + client.wakuSync.storageSize() == server.wakuSync.storageSize() + + asyncTest "server message set differences": + let msg = fakeWakuMessage(@[byte 10]) + + server.wakuSync.messageIngress(DefaultPubsubTopic, msg) + await server.wakuArchive.handleMessage(DefaultPubsubTopic, msg) + + check: + client.wakuSync.storageSize() != server.wakuSync.storageSize() + + await sleepAsync(10.milliseconds) + + check: + client.wakuSync.storageSize() == server.wakuSync.storageSize() + +suite "Waku Sync - Pruning": + var server {.threadvar.}: WakuNode + var client {.threadvar.}: WakuNode + + asyncSetup: + let + serverKey = generateSecp256k1Key() + clientKey = generateSecp256k1Key() + + server = newTestWakuNode(serverKey, IPv4_any(), Port(0)) + client = newTestWakuNode(clientKey, IPv4_any(), Port(0)) + + let serverArchiveDriver = newSqliteArchiveDriver() + let clientArchiveDriver = newSqliteArchiveDriver() + + let mountServerArchiveRes = server.mountArchive(serverArchiveDriver) + let mountClientArchiveRes = client.mountArchive(clientArchiveDriver) + + assert mountServerArchiveRes.isOk() + assert mountClientArchiveRes.isOk() + + await server.mountStore() + await client.mountStore() + + client.mountStoreClient() + server.mountStoreClient() + + let mountServerSync = await server.mountWakuSync( + maxFrameSize = 0, + relayJitter = 0.seconds, + syncRange = 1.hours, + syncInterval = 5.minutes, + ) + let mountClientSync = await client.mountWakuSync( + maxFrameSize = 0, + syncRange = 10.milliseconds, + syncInterval = 10.milliseconds, + relayJitter = 0.seconds, + ) + + assert mountServerSync.isOk(), mountServerSync.error + assert mountClientSync.isOk(), mountClientSync.error + + await allFutures(server.start(), client.start()) + + asyncTeardown: + await sleepAsync(10.milliseconds) + + await allFutures(client.stop(), server.stop()) + + asyncTest "pruning": + for _ in 0 ..< 4: + for _ in 0 ..< 10: + let msg = fakeWakuMessage() + client.wakuSync.messageIngress(DefaultPubsubTopic, msg) + await client.wakuArchive.handleMessage(DefaultPubsubTopic, msg) + + server.wakuSync.messageIngress(DefaultPubsubTopic, msg) + await server.wakuArchive.handleMessage(DefaultPubsubTopic, msg) + + await sleepAsync(10.milliseconds) + + check: + client.wakuSync.storageSize() == 10 + server.wakuSync.storageSize() == 40 diff --git a/tests/test_peer_manager.nim b/tests/test_peer_manager.nim index e4a87c14ac..d71f186cac 100644 --- a/tests/test_peer_manager.nim +++ b/tests/test_peer_manager.nim @@ -418,7 +418,8 @@ procSuite "Peer Manager": generateSecp256k1Key(), ValidIpAddress.init("0.0.0.0"), port, - pubsubTopics = @["/waku/2/rs/3/0"], + clusterId = 3, + shards = @[uint16(0)], ) # same network @@ -426,13 +427,15 @@ procSuite "Peer Manager": generateSecp256k1Key(), ValidIpAddress.init("0.0.0.0"), port, - pubsubTopics = @["/waku/2/rs/4/0"], + clusterId = 4, + shards = @[uint16(0)], ) node3 = newTestWakuNode( generateSecp256k1Key(), ValidIpAddress.init("0.0.0.0"), port, - pubsubTopics = @["/waku/2/rs/4/0"], + clusterId = 4, + shards = @[uint16(0)], ) node1.mountMetadata(3).expect("Mounted Waku Metadata") @@ -458,9 +461,9 @@ procSuite "Peer Manager": ) check: - conn1.isNone - conn2.isNone - conn3.isSome + conn1.isNone or conn1.get().isClosed + conn2.isNone or conn2.get().isClosed + conn3.isSome and not conn3.get().isClosed # TODO: nwaku/issues/1377 xasyncTest "Peer manager support multiple protocol IDs when reconnecting to peers": diff --git a/tests/test_relay_peer_exchange.nim b/tests/test_relay_peer_exchange.nim index be27365a63..0be3c91932 100644 --- a/tests/test_relay_peer_exchange.nim +++ b/tests/test_relay_peer_exchange.nim @@ -25,8 +25,8 @@ procSuite "Relay (GossipSub) Peer Exchange": newTestWakuNode(node2Key, listenAddress, port, sendSignedPeerRecord = true) # When both client and server mount relay without a handler - await node1.mountRelay(@[DefaultPubsubTopic]) - await node2.mountRelay(@[DefaultPubsubTopic], none(RoutingRecordsHandler)) + await node1.mountRelay(@[DefaultRelayShard]) + await node2.mountRelay(@[DefaultRelayShard], none(RoutingRecordsHandler)) # Then the relays are mounted without a handler check: @@ -75,9 +75,9 @@ procSuite "Relay (GossipSub) Peer Exchange": peerExchangeHandle: RoutingRecordsHandler = peerExchangeHandler # Givem the nodes mount relay with a peer exchange handler - await node1.mountRelay(@[DefaultPubsubTopic], some(emptyPeerExchangeHandle)) - await node2.mountRelay(@[DefaultPubsubTopic], some(emptyPeerExchangeHandle)) - await node3.mountRelay(@[DefaultPubsubTopic], some(peerExchangeHandle)) + await node1.mountRelay(@[DefaultRelayShard], some(emptyPeerExchangeHandle)) + await node2.mountRelay(@[DefaultRelayShard], some(emptyPeerExchangeHandle)) + await node3.mountRelay(@[DefaultRelayShard], some(peerExchangeHandle)) # Ensure that node1 prunes all peers after the first connection node1.wakuRelay.parameters.dHigh = 1 diff --git a/tests/test_waku_enr.nim b/tests/test_waku_enr.nim index fae8ae0fb9..f187fa3002 100644 --- a/tests/test_waku_enr.nim +++ b/tests/test_waku_enr.nim @@ -280,7 +280,7 @@ suite "Waku ENR - Relay static sharding": clusterId: uint16 = 22 shardId: uint16 = 1 - let topic = NsPubsubTopic.staticSharding(clusterId, shardId) + let shard = RelayShard(clusterId: clusterId, shardId: shardId) ## When let shardsTopics = RelayShards.init(clusterId, shardId).expect("Valid Shards") @@ -290,16 +290,16 @@ suite "Waku ENR - Relay static sharding": shardsTopics.clusterId == clusterId shardsTopics.shardIds == @[1u16] - let topics = shardsTopics.topics.mapIt($it) + let shards = shardsTopics.topics.mapIt($it) check: - topics == @[$topic] + shards == @[$shard] check: shardsTopics.contains(clusterId, shardId) not shardsTopics.contains(clusterId, 33u16) not shardsTopics.contains(20u16, 33u16) - shardsTopics.contains(topic) + shardsTopics.contains(shard) shardsTopics.contains("/waku/2/rs/22/1") test "new relay shards object with repeated but valid shard ids": diff --git a/tests/test_wakunode.nim b/tests/test_wakunode.nim index 4640d49f9b..975070465a 100644 --- a/tests/test_wakunode.nim +++ b/tests/test_wakunode.nim @@ -28,7 +28,7 @@ suite "WakuNode": node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(61000)) nodeKey2 = generateSecp256k1Key() node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(61002)) - pubSubTopic = "/waku/2/rs/0/0" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) @@ -36,13 +36,13 @@ suite "WakuNode": # Setup node 1 with stable codec "/vac/waku/relay/2.0.0" await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) node1.wakuRelay.codec = "/vac/waku/relay/2.0.0" # Setup node 2 with beta codec "/vac/waku/relay/2.0.0-beta2" await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) node2.wakuRelay.codec = "/vac/waku/relay/2.0.0-beta2" check: @@ -58,15 +58,15 @@ suite "WakuNode": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node2.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node2.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(2000.millis) - var res = await node1.publish(some(pubSubTopic), message) + var res = await node1.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(2000.millis) diff --git a/tests/test_wakunode_lightpush.nim b/tests/test_wakunode_lightpush.nim index 5f2c5e2df2..c680fb4685 100644 --- a/tests/test_wakunode_lightpush.nim +++ b/tests/test_wakunode_lightpush.nim @@ -19,8 +19,8 @@ suite "WakuNode - Lightpush": await allFutures(destNode.start(), bridgeNode.start(), lightNode.start()) - await destNode.mountRelay(@[DefaultPubsubTopic]) - await bridgeNode.mountRelay(@[DefaultPubsubTopic]) + await destNode.mountRelay(@[DefaultRelayShard]) + await bridgeNode.mountRelay(@[DefaultRelayShard]) await bridgeNode.mountLightPush() lightNode.mountLightPushClient() diff --git a/tests/testlib/tables.nim b/tests/testlib/tables.nim index 7db554575c..2abb2d6eb2 100644 --- a/tests/testlib/tables.nim +++ b/tests/testlib/tables.nim @@ -3,19 +3,19 @@ import std/[tables, sequtils, options] import waku/waku_core/topics, ../testlib/wakucore proc `==`*( - table: Table[pubsub_topic.NsPubsubTopic, seq[NsContentTopic]], + table: Table[pubsub_topic.RelayShard, seq[NsContentTopic]], other: array[0 .. 0, (string, seq[string])], ): bool = let otherTyped = other.map( - proc(item: (string, seq[string])): (NsPubsubTopic, seq[NsContentTopic]) = + proc(item: (string, seq[string])): (RelayShard, seq[NsContentTopic]) = let (pubsubTopic, contentTopics) = item - nsPubsubTopic = NsPubsubTopic.parse(pubsubTopic).value() + shard = RelayShard.parse(pubsubTopic).value() nsContentTopics = contentTopics.map( proc(contentTopic: string): NsContentTopic = NsContentTopic.parse(contentTopic).value() ) - return (nsPubsubTopic, nsContentTopics) + return (shard, nsContentTopics) ) table == otherTyped.toTable() diff --git a/tests/testlib/testutils.nim b/tests/testlib/testutils.nim index febebe36ba..b436c6ac4a 100644 --- a/tests/testlib/testutils.nim +++ b/tests/testlib/testutils.nim @@ -1,4 +1,4 @@ -import testutils/unittests +import testutils/unittests, chronos template xsuite*(name: string, body: untyped) = discard @@ -27,3 +27,11 @@ template xasyncTest*(name: string, body: untyped) = template asyncTestx*(name: string, body: untyped) = test name: skip() + +template waitActive*(condition: bool) = + for i in 0 ..< 200: + if condition: + break + await sleepAsync(10) + + assert condition diff --git a/tests/testlib/wakunode.nim b/tests/testlib/wakunode.nim index bec8ffe045..01e58697fa 100644 --- a/tests/testlib/wakunode.nim +++ b/tests/testlib/wakunode.nim @@ -37,8 +37,8 @@ proc defaultTestWakuNodeConf*(): WakuNodeConf = nat: "any", maxConnections: 50, maxMessageSize: "1024 KiB", - clusterId: 0, - pubsubTopics: @["/waku/2/rs/0/0"], + clusterId: DefaultClusterId, + shards: @[DefaultShardId], relay: true, storeMessageDbUrl: "sqlite://store.sqlite3", ) @@ -63,8 +63,9 @@ proc newTestWakuNode*( dns4DomainName = none(string), discv5UdpPort = none(Port), agentString = none(string), - pubsubTopics: seq[string] = @["/waku/2/rs/1/0"], peerStoreCapacity = none(int), + clusterId = DefaultClusterId, + shards = @[DefaultShardId], ): WakuNode = var resolvedExtIp = extIp @@ -77,14 +78,8 @@ proc newTestWakuNode*( var conf = defaultTestWakuNodeConf() - let clusterId = - if pubsubTopics.len() > 0: - NsPubsubTopic.parse(pubsubTopics[0]).get().clusterId - else: - 1.uint16 - conf.clusterId = clusterId - conf.pubsubTopics = pubsubTopics + conf.shards = shards if dns4DomainName.isSome() and extIp.isNone(): # If there's an error resolving the IP, an exception is thrown and test fails @@ -95,7 +90,7 @@ proc newTestWakuNode*( let netConf = NetConfig.init( bindIp = bindIp, - clusterId = clusterId, + clusterId = conf.clusterId, bindPort = bindPort, extIp = resolvedExtIp, extPort = extPort, @@ -111,8 +106,10 @@ proc newTestWakuNode*( var enrBuilder = EnrBuilder.init(nodeKey) - enrBuilder.withShardedTopics(pubsubTopics).isOkOr: - raise newException(Defect, "Invalid record: " & error) + enrBuilder.withWakuRelaySharding( + RelayShards(clusterId: conf.clusterId, shardIds: conf.shards) + ).isOkOr: + raise newException(Defect, "Invalid record: " & $error) enrBuilder.withIpAddressAndPorts( ipAddr = netConf.enrIp, tcpPort = netConf.enrPort, udpPort = netConf.discv5UdpPort @@ -140,14 +137,12 @@ proc newTestWakuNode*( if secureKey != "": some(secureKey) else: - none(string) - , + none(string), secureCert = if secureCert != "": some(secureCert) else: - none(string) - , + none(string), agentString = agentString, ) diff --git a/tests/waku_archive/test_driver_postgres_query.nim b/tests/waku_archive/test_driver_postgres_query.nim index 15c3e2c975..7a135a7a46 100644 --- a/tests/waku_archive/test_driver_postgres_query.nim +++ b/tests/waku_archive/test_driver_postgres_query.nim @@ -1788,3 +1788,32 @@ suite "Postgres driver - queries": var existsRes = await driver.existsTable("version") assert existsRes.isOk(), existsRes.error check existsRes.get() == true + + asyncTest "Query by message hash only": + const contentTopic = "test-content-topic" + + let timeOrigin = now() + let expected = + @[ + fakeWakuMessage(@[byte 0], contentTopic = contentTopic, ts = ts(00, timeOrigin)), + fakeWakuMessage(@[byte 1], contentTopic = contentTopic, ts = ts(10, timeOrigin)), + fakeWakuMessage(@[byte 2], contentTopic = contentTopic, ts = ts(20, timeOrigin)), + ] + var messages = expected + + var hashes = newSeq[WakuMessageHash](0) + for msg in messages: + let hash = computeMessageHash(DefaultPubsubTopic, msg) + hashes.add(hash) + let ret = await driver.put(hash, DefaultPubsubTopic, msg) + assert ret.isOk(), ret.error + + let ret = (await driver.getMessages(hashes = hashes)).valueOr: + assert false, $error + return + + check: + ret.len == 3 + ret[2][0] == hashes[0] + ret[1][0] == hashes[1] + ret[0][0] == hashes[2] diff --git a/tests/waku_archive_legacy/test_driver_postgres_query.nim b/tests/waku_archive_legacy/test_driver_postgres_query.nim index 05ccebafc1..29c8e07c22 100644 --- a/tests/waku_archive_legacy/test_driver_postgres_query.nim +++ b/tests/waku_archive_legacy/test_driver_postgres_query.nim @@ -1952,3 +1952,36 @@ suite "Postgres driver - queries": var existsRes = await driver.existsTable("version") assert existsRes.isOk(), existsRes.error check existsRes.get() == true + + asyncTest "Query by message hash only - legacy": + const contentTopic = "test-content-topic" + + let timeOrigin = now() + let expected = + @[ + fakeWakuMessage(@[byte 0], contentTopic = contentTopic, ts = ts(00, timeOrigin)), + fakeWakuMessage(@[byte 1], contentTopic = contentTopic, ts = ts(10, timeOrigin)), + fakeWakuMessage(@[byte 2], contentTopic = contentTopic, ts = ts(20, timeOrigin)), + ] + var messages = expected + + var hashes = newSeq[WakuMessageHash](0) + for msg in messages: + let hash = computeMessageHash(DefaultPubsubTopic, msg) + hashes.add(hash) + require ( + await driver.put( + DefaultPubsubTopic, msg, computeDigest(msg), hash, msg.timestamp + ) + ).isOk() + + let ret = (await driver.getMessages(hashes = hashes)).valueOr: + assert false, $error + return + + check: + ret.len == 3 + ## (PubsubTopic, WakuMessage, seq[byte], Timestamp, WakuMessageHash) + ret[2][4] == hashes[0] + ret[1][4] == hashes[1] + ret[0][4] == hashes[2] diff --git a/tests/waku_core/test_namespaced_topics.nim b/tests/waku_core/test_namespaced_topics.nim index 1cab7677ac..8c3cee1fc2 100644 --- a/tests/waku_core/test_namespaced_topics.nim +++ b/tests/waku_core/test_namespaced_topics.nim @@ -136,10 +136,10 @@ suite "Waku Message - Content topics namespacing": suite "Waku Message - Pub-sub topics namespacing": test "Stringify static sharding pub-sub topic": ## Given - var ns = NsPubsubTopic.staticSharding(clusterId = 0, shardId = 2) + var shard = RelayShard(clusterId: 0, shardId: 2) ## When - let topic = $ns + let topic = $shard ## Then check: @@ -150,11 +150,11 @@ suite "Waku Message - Pub-sub topics namespacing": let topic = "/waku/2/waku-dev" ## When - let nsRes = NsPubsubTopic.parse(topic) + let shardRes = RelayShard.parse(topic) ## Then - check nsRes.isErr() - let err = nsRes.tryError() + check shardRes.isErr() + let err = shardRes.tryError() check: err.kind == ParsingErrorKind.InvalidFormat @@ -163,26 +163,26 @@ suite "Waku Message - Pub-sub topics namespacing": let topic = "/waku/2/rs/16/42" ## When - let nsRes = NsPubsubTopic.parse(topic) + let shardRes = RelayShard.parse(topic) ## Then - check nsRes.isOk() + check shardRes.isOk() - let ns = nsRes.get() + let shard = shardRes.get() check: - ns.clusterId == 16 - ns.shardId == 42 + shard.clusterId == 16 + shard.shardId == 42 test "Parse pub-sub topic string - Invalid string: invalid protocol version": ## Given let topic = "/waku/1/rs/16/42" ## When - let ns = NsPubsubTopic.parse(topic) + let shard = RelayShard.parse(topic) ## Then - check ns.isErr() - let err = ns.tryError() + check shard.isErr() + let err = shard.tryError() check: err.kind == ParsingErrorKind.InvalidFormat @@ -191,11 +191,11 @@ suite "Waku Message - Pub-sub topics namespacing": let topic = "/waku/2/rs//02" ## When - let ns = NsPubsubTopic.parse(topic) + let shard = RelayShard.parse(topic) ## Then - check ns.isErr() - let err = ns.tryError() + check shard.isErr() + let err = shard.tryError() check: err.kind == ParsingErrorKind.MissingPart err.part == "cluster_id" @@ -205,10 +205,10 @@ suite "Waku Message - Pub-sub topics namespacing": let topic = "/waku/2/rs/xx/77" ## When - let ns = NsPubsubTopic.parse(topic) + let shard = RelayShard.parse(topic) ## Then - check ns.isErr() - let err = ns.tryError() + check shard.isErr() + let err = shard.tryError() check: err.kind == ParsingErrorKind.InvalidFormat diff --git a/tests/waku_core/topics/test_pubsub_topic.nim b/tests/waku_core/topics/test_pubsub_topic.nim index ba6bf65dc5..4807d30d13 100644 --- a/tests/waku_core/topics/test_pubsub_topic.nim +++ b/tests/waku_core/topics/test_pubsub_topic.nim @@ -6,15 +6,15 @@ import waku/waku_core/topics/pubsub_topic, ../../testlib/[wakucore] suite "Static Sharding Functionality": test "Shard Cluster Identification": - let topic = NsPubsubTopic.parseStaticSharding("/waku/2/rs/0/1").get() + let shard = RelayShard.parseStaticSharding("/waku/2/rs/0/1").get() check: - topic.clusterId == 0 - topic.shardId == 1 - topic == NsPubsubTopic.staticSharding(0, 1) + shard.clusterId == 0 + shard.shardId == 1 + shard == RelayShard(clusterId: 0, shardId: 1) test "Pubsub Topic Naming Compliance": - let topic = NsPubsubTopic.staticSharding(0, 1) + let shard = RelayShard(clusterId: 0, shardId: 1) check: - topic.clusterId == 0 - topic.shardId == 1 - topic == "/waku/2/rs/0/1" + shard.clusterId == 0 + shard.shardId == 1 + shard == "/waku/2/rs/0/1" diff --git a/tests/waku_core/topics/test_sharding.nim b/tests/waku_core/topics/test_sharding.nim index cae797d4dd..33c38b4304 100644 --- a/tests/waku_core/topics/test_sharding.nim +++ b/tests/waku_core/topics/test_sharding.nim @@ -41,39 +41,29 @@ suite "Autosharding": # When we generate a gen0 shard from them let - nsPubsubTopic1 = - sharding.getGenZeroShard(nsContentTopic1, GenerationZeroShardsCount) - nsPubsubTopic2 = - sharding.getGenZeroShard(nsContentTopic2, GenerationZeroShardsCount) - nsPubsubTopic3 = - sharding.getGenZeroShard(nsContentTopic3, GenerationZeroShardsCount) - nsPubsubTopic4 = - sharding.getGenZeroShard(nsContentTopic4, GenerationZeroShardsCount) - nsPubsubTopic5 = - sharding.getGenZeroShard(nsContentTopic5, GenerationZeroShardsCount) - nsPubsubTopic6 = - sharding.getGenZeroShard(nsContentTopic6, GenerationZeroShardsCount) - nsPubsubTopic7 = - sharding.getGenZeroShard(nsContentTopic7, GenerationZeroShardsCount) - nsPubsubTopic8 = - sharding.getGenZeroShard(nsContentTopic8, GenerationZeroShardsCount) - nsPubsubTopic9 = - sharding.getGenZeroShard(nsContentTopic9, GenerationZeroShardsCount) - nsPubsubTopic10 = - sharding.getGenZeroShard(nsContentTopic10, GenerationZeroShardsCount) + shard1 = sharding.getGenZeroShard(nsContentTopic1, GenerationZeroShardsCount) + shard2 = sharding.getGenZeroShard(nsContentTopic2, GenerationZeroShardsCount) + shard3 = sharding.getGenZeroShard(nsContentTopic3, GenerationZeroShardsCount) + shard4 = sharding.getGenZeroShard(nsContentTopic4, GenerationZeroShardsCount) + shard5 = sharding.getGenZeroShard(nsContentTopic5, GenerationZeroShardsCount) + shard6 = sharding.getGenZeroShard(nsContentTopic6, GenerationZeroShardsCount) + shard7 = sharding.getGenZeroShard(nsContentTopic7, GenerationZeroShardsCount) + shard8 = sharding.getGenZeroShard(nsContentTopic8, GenerationZeroShardsCount) + shard9 = sharding.getGenZeroShard(nsContentTopic9, GenerationZeroShardsCount) + shard10 = sharding.getGenZeroShard(nsContentTopic10, GenerationZeroShardsCount) # Then the generated shards are valid check: - nsPubsubTopic1 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic2 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic3 == NsPubsubTopic.staticSharding(ClusterId, 6) - nsPubsubTopic4 == NsPubsubTopic.staticSharding(ClusterId, 6) - nsPubsubTopic5 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic6 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic7 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic8 == NsPubsubTopic.staticSharding(ClusterId, 3) - nsPubsubTopic9 == NsPubsubTopic.staticSharding(ClusterId, 7) - nsPubsubTopic10 == NsPubsubTopic.staticSharding(ClusterId, 3) + shard1 == RelayShard(clusterId: ClusterId, shardId: 3) + shard2 == RelayShard(clusterId: ClusterId, shardId: 3) + shard3 == RelayShard(clusterId: ClusterId, shardId: 6) + shard4 == RelayShard(clusterId: ClusterId, shardId: 6) + shard5 == RelayShard(clusterId: ClusterId, shardId: 3) + shard6 == RelayShard(clusterId: ClusterId, shardId: 3) + shard7 == RelayShard(clusterId: ClusterId, shardId: 3) + shard8 == RelayShard(clusterId: ClusterId, shardId: 3) + shard9 == RelayShard(clusterId: ClusterId, shardId: 7) + shard10 == RelayShard(clusterId: ClusterId, shardId: 3) suite "getShard from NsContentTopic": test "Generate Gen0 Shard with topic.generation==none": @@ -81,72 +71,72 @@ suite "Autosharding": Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from a topic without generation - let nsPubsubTopic = sharding.getShard(contentTopicShort) + let shard = sharding.getShard(contentTopicShort) # Then the generated shard is valid check: - nsPubsubTopic.value() == NsPubsubTopic.staticSharding(ClusterId, 3) + shard.value() == RelayShard(clusterId: ClusterId, shardId: 3) test "Generate Gen0 Shard with topic.generation==0": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from a gen0 topic - let nsPubsubTopic = sharding.getShard(contentTopicFull) + let shard = sharding.getShard(contentTopicFull) # Then the generated shard is valid check: - nsPubsubTopic.value() == NsPubsubTopic.staticSharding(ClusterId, 3) + shard.value() == RelayShard(clusterId: ClusterId, shardId: 3) test "Generate Gen0 Shard with topic.generation==other": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from ain invalid content topic - let nsPubsubTopic = sharding.getShard(contentTopicInvalid) + let shard = sharding.getShard(contentTopicInvalid) # Then the generated shard is valid check: - nsPubsubTopic.error() == "Generation > 0 are not supported yet" + shard.error() == "Generation > 0 are not supported yet" suite "getShard from ContentTopic": test "Generate Gen0 Shard with topic.generation==none": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from it - let nsPubsubTopic = sharding.getShard(contentTopicShort) + let shard = sharding.getShard(contentTopicShort) # Then the generated shard is valid check: - nsPubsubTopic.value() == NsPubsubTopic.staticSharding(ClusterId, 3) + shard.value() == RelayShard(clusterId: ClusterId, shardId: 3) test "Generate Gen0 Shard with topic.generation==0": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from it - let nsPubsubTopic = sharding.getShard(contentTopicFull) + let shard = sharding.getShard(contentTopicFull) # Then the generated shard is valid check: - nsPubsubTopic.value() == NsPubsubTopic.staticSharding(ClusterId, 3) + shard.value() == RelayShard(clusterId: ClusterId, shardId: 3) test "Generate Gen0 Shard with topic.generation==other": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from it - let nsPubsubTopic = sharding.getShard(contentTopicInvalid) + let shard = sharding.getShard(contentTopicInvalid) # Then the generated shard is valid check: - nsPubsubTopic.error() == "Generation > 0 are not supported yet" + shard.error() == "Generation > 0 are not supported yet" test "Generate Gen0 Shard invalid topic": let sharding = Sharding(clusterId: ClusterId, shardCountGenZero: GenerationZeroShardsCount) # When we get a shard from it - let nsPubsubTopic = sharding.getShard("invalid") + let shard = sharding.getShard("invalid") # Then the generated shard is valid check: - nsPubsubTopic.error() == "invalid format: topic must start with slash" + shard.error() == "invalid format: topic must start with slash" suite "parseSharding": test "contentTopics is ContentTopic": diff --git a/tests/waku_filter_v2/test_waku_filter_dos_protection.nim b/tests/waku_filter_v2/test_waku_filter_dos_protection.nim index c584d22472..c751114c14 100644 --- a/tests/waku_filter_v2/test_waku_filter_dos_protection.nim +++ b/tests/waku_filter_v2/test_waku_filter_dos_protection.nim @@ -146,7 +146,7 @@ suite "Waku Filter - DOS protection": some(FilterSubscribeErrorKind.TOO_MANY_REQUESTS) # ensure period of time has passed and clients can again use the service - await sleepAsync(700.milliseconds) + await sleepAsync(1000.milliseconds) check client1.subscribe(serverRemotePeerInfo, pubsubTopic, contentTopicSeq) == none(FilterSubscribeErrorKind) check client2.subscribe(serverRemotePeerInfo, pubsubTopic, contentTopicSeq) == diff --git a/tests/waku_peer_exchange/test_protocol.nim b/tests/waku_peer_exchange/test_protocol.nim index e80386d5e5..6c044586a4 100644 --- a/tests/waku_peer_exchange/test_protocol.nim +++ b/tests/waku_peer_exchange/test_protocol.nim @@ -1,11 +1,10 @@ {.used.} import - std/[options, sequtils, tables], + std/[options, sequtils, tables, net], testutils/unittests, chronos, chronicles, - stew/shims/net, libp2p/[switch, peerId, crypto/crypto, multistream, muxers/muxer], eth/[keys, p2p/discoveryv5/enr] @@ -223,6 +222,7 @@ suite "Waku Peer Exchange": # Check that it failed gracefully check: response.isErr + response.error.status_code == PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE asyncTest "Request 0 peers, with 0 peers in PeerExchange": # Given a disconnected PeerExchange @@ -237,7 +237,7 @@ suite "Waku Peer Exchange": # Then the response should be an error check: response.isErr - response.error == "peer_not_found_failure" + response.error.status_code == PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE asyncTest "Pool filtering": let @@ -331,7 +331,7 @@ suite "Waku Peer Exchange": # Then the response should be an error check: response.isErr - response.error == "dial_failure" + response.error.status_code == PeerExchangeResponseStatusCode.DIAL_FAILURE asyncTest "Connections are closed after response is sent": # Create 3 nodes @@ -385,7 +385,7 @@ suite "Waku Peer Exchange": let conn = connOpt.get() # Send bytes so that they directly hit the handler - let rpc = PeerExchangeRpc(request: PeerExchangeRequest(numPeers: 1)) + let rpc = PeerExchangeRpc.makeRequest(1) var buffer: seq[byte] await conn.writeLP(rpc.encode().buffer) @@ -397,5 +397,62 @@ suite "Waku Peer Exchange": # Check we got back the enr we mocked check: + decodedBuff.get().response.status_code == PeerExchangeResponseStatusCode.SUCCESS decodedBuff.get().response.peerInfos.len == 1 decodedBuff.get().response.peerInfos[0].enr == enr1.raw + + 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)) + + # Start and mount peer exchange + await allFutures([node1.start(), node2.start()]) + await allFutures( + [ + node1.mountPeerExchange(rateLimit = (1, 150.milliseconds)), + node2.mountPeerExchange(), + ] + ) + + # Create connection + let connOpt = await node2.peerManager.dialPeer( + node1.switch.peerInfo.toRemotePeerInfo(), WakuPeerExchangeCodec + ) + require: + connOpt.isSome + + # Create some enr and add to peer exchange (simulating disv5) + var enr1, enr2 = enr.Record() + check enr1.fromUri( + "enr:-Iu4QGNuTvNRulF3A4Kb9YHiIXLr0z_CpvWkWjWKU-o95zUPR_In02AWek4nsSk7G_-YDcaT4bDRPzt5JIWvFqkXSNcBgmlkgnY0gmlwhE0WsGeJc2VjcDI1NmsxoQKp9VzU2FAh7fwOwSpg1M_Ekz4zzl0Fpbg6po2ZwgVwQYN0Y3CC6mCFd2FrdTIB" + ) + check enr2.fromUri( + "enr:-Iu4QGJllOWlviPIh_SGR-VVm55nhnBIU5L-s3ran7ARz_4oDdtJPtUs3Bc5aqZHCiPQX6qzNYF2ARHER0JPX97TFbEBgmlkgnY0gmlwhE0WsGeJc2VjcDI1NmsxoQP3ULycvday4EkvtVu0VqbBdmOkbfVLJx8fPe0lE_dRkIN0Y3CC6mCFd2FrdTIB" + ) + + # Mock that we have discovered these enrs + node1.wakuPeerExchange.enrCache.add(enr1) + node1.wakuPeerExchange.enrCache.add(enr2) + + await sleepAsync(150.milliseconds) + + # Request 2 peer from px. Test all request variants + let response1 = await node2.wakuPeerExchange.request(1) + check: + response1.isOk + response1.get().peerInfos.len == 1 + + let response2 = + await node2.wakuPeerExchange.request(1, node1.peerInfo.toRemotePeerInfo()) + check: + response2.isErr + response2.error().status_code == PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS + + await sleepAsync(150.milliseconds) + let response3 = await node2.wakuPeerExchange.request(1, connOpt.get()) + check: + response3.isOk + response3.get().peerInfos.len == 1 diff --git a/tests/waku_peer_exchange/test_rpc_codec.nim b/tests/waku_peer_exchange/test_rpc_codec.nim index 0393fb4894..84aec7ec42 100644 --- a/tests/waku_peer_exchange/test_rpc_codec.nim +++ b/tests/waku_peer_exchange/test_rpc_codec.nim @@ -1,10 +1,9 @@ {.used.} import - std/[options], + std/[options, net], testutils/unittests, chronos, - stew/shims/net, libp2p/switch, libp2p/peerId, libp2p/crypto/crypto, @@ -23,6 +22,14 @@ import suite "Peer Exchange RPC": asyncTest "Encode - Decode": # Setup + let rpcReq = PeerExchangeRpc.makeRequest(2) + let rpcReqBuffer: seq[byte] = rpcReq.encode().buffer + let resReq = PeerExchangeRpc.decode(rpcReqBuffer) + + check: + resReq.isOk + resReq.get().request.numPeers == 2 + var enr1 = enr.Record(seqNum: 0, raw: @[]) enr2 = enr.Record(seqNum: 0, raw: @[]) @@ -35,19 +42,18 @@ suite "Peer Exchange RPC": "enr:-Iu4QK_T7kzAmewG92u1pr7o6St3sBqXaiIaWIsFNW53_maJEaOtGLSN2FUbm6LmVxSfb1WfC7Eyk-nFYI7Gs3SlchwBgmlkgnY0gmlwhI5d6VKJc2VjcDI1NmsxoQLPYQDvrrFdCrhqw3JuFaGD71I8PtPfk6e7TJ3pg_vFQYN0Y3CC6mKDdWRwgiMq" ) - let - peerInfos = - @[PeerExchangePeerInfo(enr: enr1.raw), PeerExchangePeerInfo(enr: enr2.raw)] - rpc = PeerExchangeRpc(response: PeerExchangeResponse(peerInfos: peerInfos)) + let peerInfos = + @[PeerExchangePeerInfo(enr: enr1.raw), PeerExchangePeerInfo(enr: enr2.raw)] + let rpc = PeerExchangeRpc.makeResponse(peerInfos) # When encoding and decoding - let - rpcBuffer: seq[byte] = rpc.encode().buffer - res = PeerExchangeRpc.decode(rpcBuffer) + let rpcBuffer: seq[byte] = rpc.encode().buffer + let res = PeerExchangeRpc.decode(rpcBuffer) # Then the peerInfos match the originals check: res.isOk + res.get().response.status_code == PeerExchangeResponseStatusCode.SUCCESS res.get().response.peerInfos == peerInfos # When using the decoded responses to create new enrs @@ -55,6 +61,9 @@ suite "Peer Exchange RPC": resEnr1 = enr.Record(seqNum: 0, raw: @[]) resEnr2 = enr.Record(seqNum: 0, raw: @[]) + check: + res.get().response.status_code == PeerExchangeResponseStatusCode.SUCCESS + discard resEnr1.fromBytes(res.get().response.peerInfos[0].enr) discard resEnr2.fromBytes(res.get().response.peerInfos[1].enr) diff --git a/tests/waku_relay/test_wakunode_relay.nim b/tests/waku_relay/test_wakunode_relay.nim index 7c44ec90f1..b93875e831 100644 --- a/tests/waku_relay/test_wakunode_relay.nim +++ b/tests/waku_relay/test_wakunode_relay.nim @@ -63,19 +63,19 @@ suite "WakuNode - Relay": node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) nodeKey3 = generateSecp256k1Key() node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node3.start() - await node3.mountRelay(@[pubSubTopic]) + await node3.mountRelay(@[shard]) await allFutures( node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]), @@ -87,15 +87,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node3.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node3.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - var res = await node1.publish(some(pubSubTopic), message) + var res = await node1.publish(some($shard), message) assert res.isOk(), $res.error ## Then @@ -124,7 +124,7 @@ suite "WakuNode - Relay": nodeKey3 = generateSecp256k1Key() node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic1 = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message1 = WakuMessage(payload: payload, contentTopic: contentTopic1) @@ -135,13 +135,13 @@ suite "WakuNode - Relay": # start all the nodes await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node3.start() - await node3.mountRelay(@[pubSubTopic]) + await node3.mountRelay(@[shard]) await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) await node3.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) @@ -155,7 +155,7 @@ suite "WakuNode - Relay": ): Future[ValidationResult] {.async.} = ## the validator that only allows messages with contentTopic1 to be relayed check: - topic == pubSubTopic + topic == $shard # only relay messages with contentTopic1 if msg.contentTopic != contentTopic1: @@ -172,22 +172,22 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard # check that only messages with contentTopic1 is relayed (but not contentTopic2) msg.contentTopic == contentTopic1 # relay handler is called completionFut.complete(true) - node3.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node3.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - var res = await node1.publish(some(pubSubTopic), message1) + var res = await node1.publish(some($shard), message1) assert res.isOk(), $res.error await sleepAsync(500.millis) # message2 never gets relayed because of the validator - res = await node1.publish(some(pubSubTopic), message2) + res = await node1.publish(some($shard), message2) assert res.isOk(), $res.error await sleepAsync(500.millis) @@ -258,16 +258,16 @@ suite "WakuNode - Relay": wsBindPort = Port(0), wsEnabled = true, ) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) @@ -276,15 +276,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node1.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node1.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - let res = await node2.publish(some(pubSubTopic), message) + let res = await node2.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(500.millis) @@ -306,16 +306,16 @@ suite "WakuNode - Relay": ) nodeKey2 = generateSecp256k1Key() node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), bindPort = Port(0)) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) @@ -324,15 +324,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node1.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node1.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - let res = await node2.publish(some(pubSubTopic), message) + let res = await node2.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(500.millis) @@ -354,16 +354,16 @@ suite "WakuNode - Relay": wsBindPort = Port(0), wsEnabled = true, ) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) #delete websocket peer address # TODO: a better way to find the index - this is too brittle @@ -376,15 +376,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node1.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node1.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - let res = await node2.publish(some(pubSubTopic), message) + let res = await node2.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(500.millis) @@ -408,16 +408,16 @@ suite "WakuNode - Relay": ) nodeKey2 = generateSecp256k1Key() node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), bindPort = Port(0)) - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) @@ -426,15 +426,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node1.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node1.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - let res = await node2.publish(some(pubSubTopic), message) + let res = await node2.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(500.millis) @@ -466,16 +466,16 @@ suite "WakuNode - Relay": ) let - pubSubTopic = "test" + shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() message = WakuMessage(payload: payload, contentTopic: contentTopic) await node1.start() - await node1.mountRelay(@[pubSubTopic]) + await node1.mountRelay(@[shard]) await node2.start() - await node2.mountRelay(@[pubSubTopic]) + await node2.mountRelay(@[shard]) await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) @@ -484,15 +484,15 @@ suite "WakuNode - Relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = check: - topic == pubSubTopic + topic == $shard msg.contentTopic == contentTopic msg.payload == payload completionFut.complete(true) - node1.subscribe((kind: PubsubSub, topic: pubsubTopic), some(relayHandler)) + node1.subscribe((kind: PubsubSub, topic: $shard), some(relayHandler)) await sleepAsync(500.millis) - let res = await node2.publish(some(pubSubTopic), message) + let res = await node2.publish(some($shard), message) assert res.isOk(), $res.error await sleepAsync(500.millis) diff --git a/tests/waku_relay/utils.nim b/tests/waku_relay/utils.nim index f63a100827..c1a085b103 100644 --- a/tests/waku_relay/utils.nim +++ b/tests/waku_relay/utils.nim @@ -53,9 +53,9 @@ proc setupRln*(node: WakuNode, identifier: uint) {.async.} = ) proc setupRelayWithRln*( - node: WakuNode, identifier: uint, pubsubTopics: seq[string] + node: WakuNode, identifier: uint, shards: seq[RelayShard] ) {.async.} = - await node.mountRelay(pubsubTopics) + await node.mountRelay(shards) await setupRln(node, identifier) proc subscribeToContentTopicWithHandler*( diff --git a/tests/waku_rln_relay/test_waku_rln_relay.nim b/tests/waku_rln_relay/test_waku_rln_relay.nim index 4c2e854824..6768cd782f 100644 --- a/tests/waku_rln_relay/test_waku_rln_relay.nim +++ b/tests/waku_rln_relay/test_waku_rln_relay.nim @@ -502,8 +502,8 @@ suite "Waku rln relay": let (list, root) = memListRes.get() - debug "created membership key list", list - debug "the Merkle tree root", root + debug "created membership key list", number_of_keys = list.len + debug "Merkle tree root", size_calculated_tree_root = root.len check: list.len == groupSize # check the number of keys @@ -539,9 +539,9 @@ suite "Waku rln relay": let root = rawRoot.inHex() - debug "groupIdCredentials", groupIdCredentials - debug "groupIDCommitments", - groupIDCommitments = groupIDCommitments.mapIt(it.inHex()) + debug "groupIdCredentials", num_group_id_credentials = groupIdCredentials.len + # debug "groupIDCommitments", leaving commented in case needed to debug in the future + # groupIDCommitments = groupIDCommitments.mapIt(it.inHex()) debug "root", root check: diff --git a/tests/waku_rln_relay/test_wakunode_rln_relay.nim b/tests/waku_rln_relay/test_wakunode_rln_relay.nim index e227a0bb79..f837d34113 100644 --- a/tests/waku_rln_relay/test_wakunode_rln_relay.nim +++ b/tests/waku_rln_relay/test_wakunode_rln_relay.nim @@ -50,7 +50,7 @@ procSuite "WakuNode - RLN relay": # set up three nodes # node1 - await node1.mountRelay(@[DefaultPubsubTopic]) + await node1.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig1 = WakuRlnConfig( @@ -66,7 +66,7 @@ procSuite "WakuNode - RLN relay": await node1.start() # node 2 - await node2.mountRelay(@[DefaultPubsubTopic]) + await node2.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig2 = WakuRlnConfig( rlnRelayDynamic: false, @@ -81,7 +81,7 @@ procSuite "WakuNode - RLN relay": await node2.start() # node 3 - await node3.mountRelay(@[DefaultPubsubTopic]) + await node3.mountRelay(@[DefaultRelayShard]) let wakuRlnConfig3 = WakuRlnConfig( rlnRelayDynamic: false, @@ -131,18 +131,15 @@ procSuite "WakuNode - RLN relay": await node2.stop() await node3.stop() - asyncTest "testing rln-relay is applied in all rln pubsub/content topics": + asyncTest "testing rln-relay is applied in all rln shards/content topics": # create 3 nodes let nodes = toSeq(0 ..< 3).mapIt( newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) ) await allFutures(nodes.mapIt(it.start())) - let pubsubTopics = - @[ - PubsubTopic("/waku/2/pubsubtopic-a/proto"), - PubsubTopic("/waku/2/pubsubtopic-b/proto"), - ] + let shards = + @[RelayShard(clusterId: 0, shardId: 0), RelayShard(clusterId: 0, shardId: 1)] let contentTopics = @[ ContentTopic("/waku/2/content-topic-a/proto"), @@ -150,7 +147,7 @@ procSuite "WakuNode - RLN relay": ] # set up three nodes - await allFutures(nodes.mapIt(it.mountRelay(pubsubTopics))) + await allFutures(nodes.mapIt(it.mountRelay(shards))) # mount rlnrelay in off-chain mode for index, node in nodes: @@ -177,14 +174,14 @@ procSuite "WakuNode - RLN relay": topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = info "relayHandler. The received topic:", topic - if topic == pubsubTopics[0]: + if topic == $shards[0]: rxMessagesTopic1 = rxMessagesTopic1 + 1 - elif topic == pubsubTopics[1]: + elif topic == $shards[1]: rxMessagesTopic2 = rxMessagesTopic2 + 1 # mount the relay handlers - nodes[2].subscribe((kind: PubsubSub, topic: pubsubTopics[0]), some(relayHandler)) - nodes[2].subscribe((kind: PubsubSub, topic: pubsubTopics[1]), some(relayHandler)) + nodes[2].subscribe((kind: PubsubSub, topic: $shards[0]), some(relayHandler)) + nodes[2].subscribe((kind: PubsubSub, topic: $shards[1]), some(relayHandler)) await sleepAsync(1000.millis) # generate some messages with rln proofs first. generating @@ -214,9 +211,9 @@ procSuite "WakuNode - RLN relay": # publish 3 messages from node[0] (last 2 are spam, window is 10 secs) # publish 3 messages from node[1] (last 2 are spam, window is 10 secs) for msg in messages1: - discard await nodes[0].publish(some(pubsubTopics[0]), msg) + discard await nodes[0].publish(some($shards[0]), msg) for msg in messages2: - discard await nodes[1].publish(some(pubsubTopics[1]), msg) + discard await nodes[1].publish(some($shards[1]), msg) # wait for gossip to propagate await sleepAsync(5000.millis) @@ -245,7 +242,7 @@ procSuite "WakuNode - RLN relay": # set up three nodes # node1 - await node1.mountRelay(@[DefaultPubsubTopic]) + await node1.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig1 = WakuRlnConfig( @@ -261,7 +258,7 @@ procSuite "WakuNode - RLN relay": await node1.start() # node 2 - await node2.mountRelay(@[DefaultPubsubTopic]) + await node2.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig2 = WakuRlnConfig( rlnRelayDynamic: false, @@ -276,7 +273,7 @@ procSuite "WakuNode - RLN relay": await node2.start() # node 3 - await node3.mountRelay(@[DefaultPubsubTopic]) + await node3.mountRelay(@[DefaultRelayShard]) let wakuRlnConfig3 = WakuRlnConfig( rlnRelayDynamic: false, @@ -361,7 +358,7 @@ procSuite "WakuNode - RLN relay": # set up three nodes # node1 - await node1.mountRelay(@[DefaultPubsubTopic]) + await node1.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig1 = WakuRlnConfig( @@ -377,7 +374,7 @@ procSuite "WakuNode - RLN relay": await node1.start() # node 2 - await node2.mountRelay(@[DefaultPubsubTopic]) + await node2.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig2 = WakuRlnConfig( @@ -392,7 +389,7 @@ procSuite "WakuNode - RLN relay": await node2.start() # node 3 - await node3.mountRelay(@[DefaultPubsubTopic]) + await node3.mountRelay(@[DefaultRelayShard]) # mount rlnrelay in off-chain mode let wakuRlnConfig3 = WakuRlnConfig( @@ -485,7 +482,7 @@ procSuite "WakuNode - RLN relay": # Given two nodes let contentTopic = ContentTopic("/waku/2/default-content/proto") - pubsubTopicSeq = @[DefaultPubsubTopic] + shardSeq = @[DefaultRelayShard] nodeKey1 = generateSecp256k1Key() node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) nodeKey2 = generateSecp256k1Key() @@ -493,12 +490,12 @@ procSuite "WakuNode - RLN relay": epochSizeSec: uint64 = 5 # This means rlnMaxEpochGap = 4 # Given both nodes mount relay and rlnrelay - await node1.mountRelay(pubsubTopicSeq) + await node1.mountRelay(shardSeq) let wakuRlnConfig1 = buildWakuRlnConfig(1, epochSizeSec, "wakunode_10") await node1.mountRlnRelay(wakuRlnConfig1) # Mount rlnrelay in node2 in off-chain mode - await node2.mountRelay(@[DefaultPubsubTopic]) + await node2.mountRelay(@[DefaultRelayShard]) let wakuRlnConfig2 = buildWakuRlnConfig(2, epochSizeSec, "wakunode_11") await node2.mountRlnRelay(wakuRlnConfig2) @@ -613,7 +610,7 @@ procSuite "WakuNode - RLN relay": # Given two nodes let contentTopic = ContentTopic("/waku/2/default-content/proto") - pubsubTopicSeq = @[DefaultPubsubTopic] + shardSeq = @[DefaultRelayShard] nodeKey1 = generateSecp256k1Key() node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) nodeKey2 = generateSecp256k1Key() @@ -622,12 +619,12 @@ procSuite "WakuNode - RLN relay": # Given both nodes mount relay and rlnrelay # Mount rlnrelay in node1 in off-chain mode - await node1.mountRelay(pubsubTopicSeq) + await node1.mountRelay(shardSeq) let wakuRlnConfig1 = buildWakuRlnConfig(1, epochSizeSec, "wakunode_10") await node1.mountRlnRelay(wakuRlnConfig1) # Mount rlnrelay in node2 in off-chain mode - await node2.mountRelay(@[DefaultPubsubTopic]) + await node2.mountRelay(@[DefaultRelayShard]) let wakuRlnConfig2 = buildWakuRlnConfig(2, epochSizeSec, "wakunode_11") await node2.mountRlnRelay(wakuRlnConfig2) diff --git a/tests/waku_rln_relay/utils_static.nim b/tests/waku_rln_relay/utils_static.nim index 00f29c7048..d2a781fcdb 100644 --- a/tests/waku_rln_relay/utils_static.nim +++ b/tests/waku_rln_relay/utils_static.nim @@ -32,9 +32,9 @@ proc setupStaticRln*( ) proc setupRelayWithStaticRln*( - node: WakuNode, identifier: uint, pubsubTopics: seq[string] + node: WakuNode, identifier: uint, shards: seq[RelayShard] ) {.async.} = - await node.mountRelay(pubsubTopics) + await node.mountRelay(shards) await setupStaticRln(node, identifier) proc subscribeCompletionHandler*(node: WakuNode, pubsubTopic: string): Future[bool] = diff --git a/tests/waku_store_legacy/test_client.nim b/tests/waku_store_legacy/test_client.nim index 57099e9957..9e403dc214 100644 --- a/tests/waku_store_legacy/test_client.nim +++ b/tests/waku_store_legacy/test_client.nim @@ -44,6 +44,7 @@ suite "Store Client": pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[DefaultContentTopic], direction: PagingDirection.FORWARD, + requestId: "customRequestId", ) serverSwitch = newTestSwitch() @@ -93,33 +94,39 @@ suite "Store Client": pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[], direction: PagingDirection.FORWARD, + requestId: "reqId1", ) invalidQuery2 = HistoryQuery( pubsubTopic: PubsubTopic.none(), contentTopics: @[DefaultContentTopic], direction: PagingDirection.FORWARD, + requestId: "reqId2", ) invalidQuery3 = HistoryQuery( pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[DefaultContentTopic], pageSize: 0, + requestId: "reqId3", ) invalidQuery4 = HistoryQuery( pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[DefaultContentTopic], pageSize: 0, + requestId: "reqId4", ) invalidQuery5 = HistoryQuery( pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[DefaultContentTopic], startTime: some(0.Timestamp), endTime: some(0.Timestamp), + requestId: "reqId5", ) invalidQuery6 = HistoryQuery( pubsubTopic: some(DefaultPubsubTopic), contentTopics: @[DefaultContentTopic], startTime: some(0.Timestamp), endTime: some(-1.Timestamp), + requestId: "reqId6", ) # When the query is sent to the server diff --git a/tests/waku_store_legacy/test_waku_store.nim b/tests/waku_store_legacy/test_waku_store.nim index a134645a9c..8ff4eaf090 100644 --- a/tests/waku_store_legacy/test_waku_store.nim +++ b/tests/waku_store_legacy/test_waku_store.nim @@ -44,7 +44,9 @@ suite "Waku Store - query handler legacy": client = newTestWakuStoreClient(clientSwitch) let req = HistoryQuery( - contentTopics: @[DefaultContentTopic], direction: PagingDirection.FORWARD + contentTopics: @[DefaultContentTopic], + direction: PagingDirection.FORWARD, + requestId: "reqId", ) ## When @@ -96,7 +98,9 @@ suite "Waku Store - query handler legacy": client = newTestWakuStoreClient(clientSwitch) let req = HistoryQuery( - contentTopics: @[DefaultContentTopic], direction: PagingDirection.FORWARD + contentTopics: @[DefaultContentTopic], + direction: PagingDirection.FORWARD, + requestId: "reqId", ) info "check point" # log added to track flaky test diff --git a/tests/waku_sync/sync_utils.nim b/tests/waku_sync/sync_utils.nim new file mode 100644 index 0000000000..be0e44d7e5 --- /dev/null +++ b/tests/waku_sync/sync_utils.nim @@ -0,0 +1,37 @@ +{.used.} + +import std/options, chronos, chronicles, libp2p/crypto/crypto + +import waku/[node/peer_manager, waku_core, waku_sync], ../testlib/wakucore + +proc newTestWakuSync*( + switch: Switch, + transfer: Option[TransferCallback] = none(TransferCallback), + prune: Option[PruneCallback] = none(PruneCallback), + interval: Duration = DefaultSyncInterval, +): Future[WakuSync] {.async.} = + let peerManager = PeerManager.new(switch) + + let fakePruneCallback = proc( + pruneStart: Timestamp, pruneStop: Timestamp, cursor: Option[WakuMessageHash] + ): Future[ + Result[(seq[(WakuMessageHash, Timestamp)], Option[WakuMessageHash]), string] + ] {.async: (raises: []), closure.} = + return ok((@[], none(WakuMessageHash))) + + let res = await WakuSync.new( + peerManager = peerManager, + relayJitter = 0.seconds, + syncInterval = interval, + wakuArchive = nil, + wakuStoreClient = nil, + pruneCallback = some(fakePruneCallback), + transferCallback = none(TransferCallback), + ) + + let proto = res.get() + + proto.start() + switch.mount(proto) + + return proto diff --git a/tests/waku_sync/test_all.nim b/tests/waku_sync/test_all.nim new file mode 100644 index 0000000000..b5801e4acb --- /dev/null +++ b/tests/waku_sync/test_all.nim @@ -0,0 +1,3 @@ +{.used.} + +import ./test_protocol, ./test_bindings diff --git a/tests/waku_sync/test_bindings.nim b/tests/waku_sync/test_bindings.nim new file mode 100644 index 0000000000..f2099ea50a --- /dev/null +++ b/tests/waku_sync/test_bindings.nim @@ -0,0 +1,141 @@ +{.used.} + +import std/[options], testutils/unittests, chronos, libp2p/crypto/crypto, std/random + +import + ../../waku/ + [node/peer_manager, waku_core, waku_core/message/digest, waku_sync/raw_bindings], + ../testlib/[wakucore], + ./sync_utils + +random.randomize() + +#TODO clean this up + +suite "Bindings": + var storage {.threadvar.}: NegentropyStorage + var messages {.threadvar.}: seq[(WakuMessageHash, WakuMessage)] + + setup: + let storageRes = NegentropyStorage.new() + assert storageRes.isOk(), $storageRes.error + storage = storageRes.get() + + messages = @[] + for _ in 0 ..< 10: + let msg = fakeWakuMessage() + let hash = computeMessageHash(DefaultPubsubTopic, msg) + messages.add((hash, msg)) + + teardown: + storage.delete() + + test "storage insert": + check: + storage.len() == 0 + + let insRes = storage.insert(messages[0][1].timestamp, messages[0][0]) + + assert insRes.isOk(), $insRes.error + + check: + storage.len() == 1 + + test "storage erase": + let insRes = storage.insert(messages[0][1].timestamp, messages[0][0]) + assert insRes.isOk(), $insRes.error + + check: + storage.len() == 1 + + var delRes = storage.erase(messages[0][1].timestamp, messages[0][0]) + assert delRes.isOk() + + check: + storage.len() == 0 + + delRes = storage.erase(messages[0][1].timestamp, messages[0][0]) + assert delRes.isErr() + + check: + storage.len() == 0 + + test "subrange": + for (hash, msg) in messages: + let insRes = storage.insert(msg.timestamp, hash) + assert insRes.isOk(), $insRes.error + + check: + storage.len() == 10 + + let subrangeRes = NegentropySubRangeStorage.new(storage) + assert subrangeRes.isOk(), subrangeRes.error + let subrange = subrangeRes.get() + + check: + subrange.len() == 10 + + #[ test "storage memory size": + for (hash, msg) in messages: + let insRes = storage.insert(msg.timestamp, hash) + assert insRes.isOk(), $insRes.error + + check: + storage.len() == 10 + + for (hash, msg) in messages: + let delRes = storage.erase(msg.timestamp, hash) + assert delRes.isOk(), $delRes.error + + check: + storage.len() == 0 + + #TODO validate that the occupied memory didn't grow. ]# + + test "reconcile server differences": + for (hash, msg) in messages: + let insRes = storage.insert(msg.timestamp, hash) + assert insRes.isOk(), $insRes.error + + let clientNegentropyRes = Negentropy.new(storage, 0) + + let storageRes = NegentropyStorage.new() + assert storageRes.isOk(), $storageRes.error + let serverStorage = storageRes.get() + + for (hash, msg) in messages: + let insRes = serverStorage.insert(msg.timestamp, hash) + assert insRes.isOk(), $insRes.error + + # the extra msg + let msg = fakeWakuMessage() + let hash = computeMessageHash(DefaultPubsubTopic, msg) + let insRes = serverStorage.insert(msg.timestamp, hash) + assert insRes.isOk(), $insRes.error + + let serverNegentropyRes = Negentropy.new(serverStorage, 0) + + assert clientNegentropyRes.isOk(), $clientNegentropyRes.error + assert serverNegentropyRes.isOk(), $serverNegentropyRes.error + + let clientNegentropy = clientNegentropyRes.get() + let serverNegentropy = serverNegentropyRes.get() + + let initRes = clientNegentropy.initiate() + assert initRes.isOk(), $initRes.error + let init = initRes.get() + + let reconRes = serverNegentropy.serverReconcile(init) + assert reconRes.isOk(), $reconRes.error + let srecon = reconRes.get() + + var + haves: seq[WakuMessageHash] + needs: seq[WakuMessageHash] + let creconRes = clientNegentropy.clientReconcile(srecon, haves, needs) + assert creconRes.isOk(), $creconRes.error + let reconOpt = creconRes.get() + + check: + reconOpt.isNone() + needs[0] == hash diff --git a/tests/waku_sync/test_protocol.nim b/tests/waku_sync/test_protocol.nim new file mode 100644 index 0000000000..c203471fb2 --- /dev/null +++ b/tests/waku_sync/test_protocol.nim @@ -0,0 +1,374 @@ +{.used.} + +import + std/[options, sets], + testutils/unittests, + chronos, + chronicles, + libp2p/crypto/crypto, + stew/byteutils, + std/random + +import + ../../waku/[ + node/peer_manager, + waku_core, + waku_core/message/digest, + waku_sync, + waku_sync/raw_bindings, + ], + ../testlib/[wakucore, testasync], + ./sync_utils + +random.randomize() + +suite "Waku Sync": + var serverSwitch {.threadvar.}: Switch + var clientSwitch {.threadvar.}: Switch + + var server {.threadvar.}: WakuSync + var client {.threadvar.}: WakuSync + + var serverPeerInfo {.threadvar.}: Option[RemotePeerInfo] + + asyncSetup: + serverSwitch = newTestSwitch() + clientSwitch = newTestSwitch() + + await allFutures(serverSwitch.start(), clientSwitch.start()) + + server = await newTestWakuSync(serverSwitch) + client = await newTestWakuSync(clientSwitch) + + serverPeerInfo = some(serverSwitch.peerInfo.toRemotePeerInfo()) + + asyncTeardown: + await sleepAsync(10.milliseconds) + + await allFutures(server.stop(), client.stop()) + await allFutures(serverSwitch.stop(), clientSwitch.stop()) + + suite "Protocol": + asyncTest "sync 2 nodes both empty": + let hashes = await client.storeSynchronization(serverPeerInfo) + assert hashes.isOk(), hashes.error + check: + hashes.value[0].len == 0 + + asyncTest "sync 2 nodes empty client full server": + let msg1 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg2 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg3 = fakeWakuMessage(contentTopic = DefaultContentTopic) + + server.messageIngress(DefaultPubsubTopic, msg1) + server.messageIngress(DefaultPubsubTopic, msg2) + server.messageIngress(DefaultPubsubTopic, msg3) + + var hashes = await client.storeSynchronization(serverPeerInfo) + + assert hashes.isOk(), hashes.error + check: + hashes.value[0].len == 3 + computeMessageHash(pubsubTopic = DefaultPubsubTopic, msg1) in hashes.value[0] + computeMessageHash(pubsubTopic = DefaultPubsubTopic, msg2) in hashes.value[0] + computeMessageHash(pubsubTopic = DefaultPubsubTopic, msg3) in hashes.value[0] + + asyncTest "sync 2 nodes full client empty server": + let msg1 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg2 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg3 = fakeWakuMessage(contentTopic = DefaultContentTopic) + + client.messageIngress(DefaultPubsubTopic, msg1) + client.messageIngress(DefaultPubsubTopic, msg2) + client.messageIngress(DefaultPubsubTopic, msg3) + + var hashes = await client.storeSynchronization(serverPeerInfo) + assert hashes.isOk(), hashes.error + check: + hashes.value[0].len == 0 + + asyncTest "sync 2 nodes different hashes": + let msg1 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg2 = fakeWakuMessage(contentTopic = DefaultContentTopic) + + server.messageIngress(DefaultPubsubTopic, msg1) + client.messageIngress(DefaultPubsubTopic, msg1) + server.messageIngress(DefaultPubsubTopic, msg2) + + var syncRes = await client.storeSynchronization(serverPeerInfo) + + check: + syncRes.isOk() + + var hashes = syncRes.get() + + check: + hashes[0].len == 1 + hashes[0][0] == computeMessageHash(pubsubTopic = DefaultPubsubTopic, msg2) + + #Assuming message is fetched from peer + client.messageIngress(DefaultPubsubTopic, msg2) + + syncRes = await client.storeSynchronization(serverPeerInfo) + + check: + syncRes.isOk() + + hashes = syncRes.get() + + check: + hashes[0].len == 0 + + asyncTest "sync 2 nodes same hashes": + let msg1 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let msg2 = fakeWakuMessage(contentTopic = DefaultContentTopic) + + server.messageIngress(DefaultPubsubTopic, msg1) + client.messageIngress(DefaultPubsubTopic, msg1) + server.messageIngress(DefaultPubsubTopic, msg2) + client.messageIngress(DefaultPubsubTopic, msg2) + + let hashes = await client.storeSynchronization(serverPeerInfo) + assert hashes.isOk(), $hashes.error + check: + hashes.value[0].len == 0 + + asyncTest "sync 2 nodes 100K msgs": + var i = 0 + let msgCount = 100000 + var diffIndex = rand(msgCount) + var diffMsg: WakuMessage + while i < msgCount: + let msg = fakeWakuMessage(contentTopic = DefaultContentTopic) + if i != diffIndex: + client.messageIngress(DefaultPubsubTopic, msg) + else: + diffMsg = msg + server.messageIngress(DefaultPubsubTopic, msg) + i += 1 + + let hashes = await client.storeSynchronization(serverPeerInfo) + assert hashes.isOk(), $hashes.error + + check: + hashes.value[0].len == 1 + hashes.value[0][0] == computeMessageHash(DefaultPubsubTopic, diffMsg) + + asyncTest "sync 2 nodes 100K msgs 10K diffs": + var i = 0 + let msgCount = 100000 + var diffCount = 10000 + + var diffMsgHashes: seq[WakuMessageHash] + var randIndexes: seq[int] + while i < diffCount: + let randInt = rand(msgCount) + if randInt in randIndexes: + continue + randIndexes.add(randInt) + i += 1 + + i = 0 + var tmpDiffCnt = diffCount + while i < msgCount: + let msg = fakeWakuMessage(contentTopic = DefaultContentTopic) + if tmpDiffCnt > 0 and i in randIndexes: + diffMsgHashes.add(computeMessageHash(DefaultPubsubTopic, msg)) + tmpDiffCnt = tmpDiffCnt - 1 + else: + client.messageIngress(DefaultPubsubTopic, msg) + + server.messageIngress(DefaultPubsubTopic, msg) + i += 1 + + let hashes = await client.storeSynchronization(serverPeerInfo) + assert hashes.isOk(), $hashes.error + + check: + hashes.value[0].len == diffCount + toHashSet(hashes.value[0]) == toHashSet(diffMsgHashes) + + asyncTest "sync 3 nodes 2 client 1 server": + ## Setup + let client2Switch = newTestSwitch() + await client2Switch.start() + let client2 = await newTestWakuSync(client2Switch) + + let msgCount = 10000 + var i = 0 + + while i < msgCount: + i += 1 + let msg = fakeWakuMessage(contentTopic = DefaultContentTopic) + if i mod 2 == 0: + client2.messageIngress(DefaultPubsubTopic, msg) + else: + client.messageIngress(DefaultPubsubTopic, msg) + server.messageIngress(DefaultPubsubTopic, msg) + + let fut1 = client.storeSynchronization(serverPeerInfo) + let fut2 = client2.storeSynchronization(serverPeerInfo) + waitFor allFutures(fut1, fut2) + + let hashes1 = fut1.read() + let hashes2 = fut2.read() + + assert hashes1.isOk(), $hashes1.error + assert hashes2.isOk(), $hashes2.error + + check: + hashes1.value[0].len == int(msgCount / 2) + hashes2.value[0].len == int(msgCount / 2) + + await client2.stop() + await client2Switch.stop() + + asyncTest "sync 6 nodes varying sync diffs": + ## Setup + let + client2Switch = newTestSwitch() + client3Switch = newTestSwitch() + client4Switch = newTestSwitch() + client5Switch = newTestSwitch() + + await allFutures( + client2Switch.start(), + client3Switch.start(), + client4Switch.start(), + client5Switch.start(), + ) + + let + client2 = await newTestWakuSync(client2Switch) + client3 = await newTestWakuSync(client3Switch) + client4 = await newTestWakuSync(client4Switch) + client5 = await newTestWakuSync(client5Switch) + + let msgCount = 100000 + var i = 0 + + while i < msgCount: + let msg = fakeWakuMessage(contentTopic = DefaultContentTopic) + if i < msgCount - 1: + client.messageIngress(DefaultPubsubTopic, msg) + if i < msgCount - 10: + client2.messageIngress(DefaultPubsubTopic, msg) + if i < msgCount - 100: + client3.messageIngress(DefaultPubsubTopic, msg) + if i < msgCount - 1000: + client4.messageIngress(DefaultPubsubTopic, msg) + if i < msgCount - 10000: + client5.messageIngress(DefaultPubsubTopic, msg) + server.messageIngress(DefaultPubsubTopic, msg) + i += 1 + + var timeBefore = getNowInNanosecondTime() + let hashes1 = await client.storeSynchronization(serverPeerInfo) + var timeAfter = getNowInNanosecondTime() + var syncTime = (timeAfter - timeBefore) + debug "sync time in seconds", msgsTotal = msgCount, diff = 1, syncTime = syncTime + assert hashes1.isOk(), $hashes1.error + check: + hashes1.value[0].len == 1 + + timeBefore = getNowInNanosecondTime() + let hashes2 = await client2.storeSynchronization(serverPeerInfo) + timeAfter = getNowInNanosecondTime() + syncTime = (timeAfter - timeBefore) + debug "sync time in seconds", msgsTotal = msgCount, diff = 10, syncTime = syncTime + assert hashes2.isOk(), $hashes2.error + check: + hashes2.value[0].len == 10 + + timeBefore = getNowInNanosecondTime() + let hashes3 = await client3.storeSynchronization(serverPeerInfo) + timeAfter = getNowInNanosecondTime() + syncTime = (timeAfter - timeBefore) + debug "sync time in seconds", + msgsTotal = msgCount, diff = 100, syncTime = syncTime + assert hashes3.isOk(), $hashes3.error + check: + hashes3.value[0].len == 100 + + timeBefore = getNowInNanosecondTime() + let hashes4 = await client4.storeSynchronization(serverPeerInfo) + timeAfter = getNowInNanosecondTime() + syncTime = (timeAfter - timeBefore) + debug "sync time in seconds", + msgsTotal = msgCount, diff = 1000, syncTime = syncTime + assert hashes4.isOk(), $hashes4.error + check: + hashes4.value[0].len == 1000 + + timeBefore = getNowInNanosecondTime() + let hashes5 = await client5.storeSynchronization(serverPeerInfo) + timeAfter = getNowInNanosecondTime() + syncTime = (timeAfter - timeBefore) + debug "sync time in seconds", + msgsTotal = msgCount, diff = 10000, syncTime = syncTime + assert hashes5.isOk(), $hashes5.error + check: + hashes5.value[0].len == 10000 + + await allFutures(client2.stop(), client3.stop(), client4.stop(), client5.stop()) + await allFutures( + client2Switch.stop(), + client3Switch.stop(), + client4Switch.stop(), + client5Switch.stop(), + ) + + asyncTest "sync 3 nodes cyclic": + let + node1Switch = newTestSwitch() + node2Switch = newTestSwitch() + node3Switch = newTestSwitch() + + await allFutures(node1Switch.start(), node2Switch.start(), node3Switch.start()) + + let node1PeerInfo = some(node1Switch.peerInfo.toRemotePeerInfo()) + let node2PeerInfo = some(node2Switch.peerInfo.toRemotePeerInfo()) + let node3PeerInfo = some(node3Switch.peerInfo.toRemotePeerInfo()) + + let msg1 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let hash1 = computeMessageHash(DefaultPubsubTopic, msg1) + let msg2 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let hash2 = computeMessageHash(DefaultPubsubTopic, msg2) + let msg3 = fakeWakuMessage(contentTopic = DefaultContentTopic) + let hash3 = computeMessageHash(DefaultPubsubTopic, msg3) + + let + node1 = await newTestWakuSync(node1Switch) + node2 = await newTestWakuSync(node2Switch) + node3 = await newTestWakuSync(node3Switch) + + node1.messageIngress(DefaultPubsubTopic, msg1) + node2.messageIngress(DefaultPubsubTopic, msg1) + node2.messageIngress(DefaultPubsubTopic, msg2) + node3.messageIngress(DefaultPubsubTopic, msg3) + + let f1 = node1.storeSynchronization(node2PeerInfo) + let f2 = node2.storeSynchronization(node3PeerInfo) + let f3 = node3.storeSynchronization(node1PeerInfo) + + waitFor allFutures(f1, f2, f3) + + let hashes1 = f1.read() + let hashes2 = f2.read() + let hashes3 = f3.read() + + assert hashes1.isOk(), hashes1.error + assert hashes2.isOk(), hashes2.error + assert hashes3.isOk(), hashes3.error + + check: + hashes1.get()[0].len == 1 + hashes2.get()[0].len == 1 + hashes3.get()[0].len == 1 + + hashes1.get()[0][0] == hash2 + hashes2.get()[0][0] == hash3 + hashes3.get()[0][0] == hash1 + + await allFutures(node1.stop(), node2.stop(), node3.stop()) + await allFutures(node1Switch.stop(), node2Switch.stop(), node3Switch.stop()) diff --git a/tests/wakunode2/test_app.nim b/tests/wakunode2/test_app.nim index 6228fef3fa..04057b1a1f 100644 --- a/tests/wakunode2/test_app.nim +++ b/tests/wakunode2/test_app.nim @@ -17,7 +17,7 @@ include waku/factory/waku suite "Wakunode2 - Waku": test "compilation version should be reported": ## Given - let conf = defaultTestWakuNodeConf() + var conf = defaultTestWakuNodeConf() let waku = Waku.init(conf).valueOr: raiseAssert error @@ -43,7 +43,7 @@ suite "Wakunode2 - Waku initialization": test "node setup is successful with default configuration": ## Given - let conf = defaultTestWakuNodeConf() + var conf = defaultTestWakuNodeConf() ## When var waku = Waku.init(conf).valueOr: diff --git a/tests/wakunode2/test_validators.nim b/tests/wakunode2/test_validators.nim index 5c30d14667..058d2bedf4 100644 --- a/tests/wakunode2/test_validators.nim +++ b/tests/wakunode2/test_validators.nim @@ -31,14 +31,14 @@ suite "WakuNode2 - Validators": newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) ) - # Protected topic and key to sign - let spamProtectedTopic = PubSubTopic("some-spam-protected-topic") + # Protected shard and key to sign + let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) let secretKey = SkSecretKey .fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6") .expect("valid key") let publicKey = secretKey.toPublicKey() - let topicsPrivateKeys = {spamProtectedTopic: secretKey}.toTable - let topicsPublicKeys = {spamProtectedTopic: publicKey}.toTable + let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable + let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable # Start all the nodes and mount relay with protected topic await allFutures(nodes.mapIt(it.start())) @@ -48,10 +48,12 @@ suite "WakuNode2 - Validators": # Add signed message validator to all nodes. They will only route signed messages for node in nodes: - var signedTopics: seq[ProtectedTopic] - for topic, publicKey in topicsPublicKeys: - signedTopics.add(ProtectedTopic(topic: topic, key: publicKey)) - node.wakuRelay.addSignedTopicsValidator(signedTopics) + var signedShards: seq[ProtectedShard] + for shard, publicKey in shardsPublicKeys: + signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey)) + node.wakuRelay.addSignedShardsValidator( + signedShards, spamProtectedShard.clusterId + ) # Connect the nodes in a full mesh for i in 0 ..< 5: @@ -72,7 +74,7 @@ suite "WakuNode2 - Validators": # Subscribe all nodes to the same topic/handler for node in nodes: - discard node.wakuRelay.subscribe(spamProtectedTopic, handler) + discard node.wakuRelay.subscribe($spamProtectedShard, handler) await sleepAsync(500.millis) # Each node publishes 10 signed messages @@ -80,7 +82,7 @@ suite "WakuNode2 - Validators": for j in 0 ..< 10: var msg = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: now(), ephemeral: true, @@ -88,9 +90,9 @@ suite "WakuNode2 - Validators": # Include signature msg.meta = - secretKey.sign(SkMessage(spamProtectedTopic.msgHash(msg))).toRaw()[0 .. 63] + secretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[0 .. 63] - discard await nodes[i].publish(some(spamProtectedTopic), msg) + discard await nodes[i].publish(some($spamProtectedShard), msg) # Wait for gossip await sleepAsync(2.seconds) @@ -103,7 +105,7 @@ suite "WakuNode2 - Validators": for i in 0 ..< 5: for k, v in nodes[i].wakuRelay.peerStats.mpairs: check: - v.topicInfos[spamProtectedTopic].invalidMessageDeliveries == 0.0 + v.topicInfos[spamProtectedShard].invalidMessageDeliveries == 0.0 # Stop all nodes await allFutures(nodes.mapIt(it.stop())) @@ -114,14 +116,14 @@ suite "WakuNode2 - Validators": newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) ) - # Protected topic and key to sign - let spamProtectedTopic = PubSubTopic("some-spam-protected-topic") + # Protected shard and key to sign + let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) let secretKey = SkSecretKey .fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6") .expect("valid key") let publicKey = secretKey.toPublicKey() - let topicsPrivateKeys = {spamProtectedTopic: secretKey}.toTable - let topicsPublicKeys = {spamProtectedTopic: publicKey}.toTable + let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable + let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable # Non whitelisted secret key let wrongSecretKey = SkSecretKey @@ -136,10 +138,12 @@ suite "WakuNode2 - Validators": # Add signed message validator to all nodes. They will only route signed messages for node in nodes: - var signedTopics: seq[ProtectedTopic] - for topic, publicKey in topicsPublicKeys: - signedTopics.add(ProtectedTopic(topic: topic, key: publicKey)) - node.wakuRelay.addSignedTopicsValidator(signedTopics) + var signedShards: seq[ProtectedShard] + for shard, publicKey in shardsPublicKeys: + signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey)) + node.wakuRelay.addSignedShardsValidator( + signedShards, spamProtectedShard.clusterId + ) # Connect the nodes in a full mesh for i in 0 ..< 5: @@ -160,7 +164,7 @@ suite "WakuNode2 - Validators": # Subscribe all nodes to the same topic/handler for node in nodes: - discard node.wakuRelay.subscribe(spamProtectedTopic, handler) + discard node.wakuRelay.subscribe($spamProtectedShard, handler) await sleepAsync(500.millis) # Each node sends 5 messages, signed but with a non-whitelisted key (total = 25) @@ -168,42 +172,42 @@ suite "WakuNode2 - Validators": for j in 0 ..< 5: var msg = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: now(), ephemeral: true, ) # Sign the message with a wrong key - msg.meta = wrongSecretKey.sign(SkMessage(spamProtectedTopic.msgHash(msg))).toRaw()[ + msg.meta = wrongSecretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[ 0 .. 63 ] - discard await nodes[i].publish(some(spamProtectedTopic), msg) + discard await nodes[i].publish(some($spamProtectedShard), msg) # Each node sends 5 messages that are not signed (total = 25) for i in 0 ..< 5: for j in 0 ..< 5: let unsignedMessage = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: now(), ephemeral: true, ) - discard await nodes[i].publish(some(spamProtectedTopic), unsignedMessage) + discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage) # Each node sends 5 messages that dont contain timestamp (total = 25) for i in 0 ..< 5: for j in 0 ..< 5: let unsignedMessage = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: 0, ephemeral: true, ) - discard await nodes[i].publish(some(spamProtectedTopic), unsignedMessage) + discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage) # Each node sends 5 messages way BEFORE than the current timestmap (total = 25) for i in 0 ..< 5: @@ -211,12 +215,12 @@ suite "WakuNode2 - Validators": let beforeTimestamp = now() - getNanosecondTime(6 * 60) let unsignedMessage = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: beforeTimestamp, ephemeral: true, ) - discard await nodes[i].publish(some(spamProtectedTopic), unsignedMessage) + discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage) # Each node sends 5 messages way LATER than the current timestmap (total = 25) for i in 0 ..< 5: @@ -224,12 +228,12 @@ suite "WakuNode2 - Validators": let afterTimestamp = now() - getNanosecondTime(6 * 60) let unsignedMessage = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: afterTimestamp, ephemeral: true, ) - discard await nodes[i].publish(some(spamProtectedTopic), unsignedMessage) + discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage) # Since we have a full mesh with 5 nodes and each one publishes 25+25+25+25+25 msgs # there are 625 messages being sent. @@ -243,7 +247,7 @@ suite "WakuNode2 - Validators": msgRejected = 0 for i in 0 ..< 5: for k, v in nodes[i].wakuRelay.peerStats.mpairs: - msgRejected += v.topicInfos[spamProtectedTopic].invalidMessageDeliveries.int + msgRejected += v.topicInfos[spamProtectedShard].invalidMessageDeliveries.int if msgReceived == 125 and msgRejected == 500: break @@ -262,14 +266,14 @@ suite "WakuNode2 - Validators": newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) ) - # Protected topic and key to sign - let spamProtectedTopic = PubSubTopic("some-spam-protected-topic") + # Protected shard and key to sign + let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) let secretKey = SkSecretKey .fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6") .expect("valid key") let publicKey = secretKey.toPublicKey() - let topicsPrivateKeys = {spamProtectedTopic: secretKey}.toTable - let topicsPublicKeys = {spamProtectedTopic: publicKey}.toTable + let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable + let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable # Non whitelisted secret key let wrongSecretKey = SkSecretKey @@ -288,15 +292,17 @@ suite "WakuNode2 - Validators": # Subscribe all nodes to the same topic/handler for node in nodes: - discard node.wakuRelay.subscribe(spamProtectedTopic, handler) + discard node.wakuRelay.subscribe($spamProtectedShard, handler) await sleepAsync(500.millis) # Add signed message validator to all nodes. They will only route signed messages for node in nodes: - var signedTopics: seq[ProtectedTopic] - for topic, publicKey in topicsPublicKeys: - signedTopics.add(ProtectedTopic(topic: topic, key: publicKey)) - node.wakuRelay.addSignedTopicsValidator(signedTopics) + var signedShards: seq[ProtectedShard] + for shard, publicKey in shardsPublicKeys: + signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey)) + node.wakuRelay.addSignedShardsValidator( + signedShards, spamProtectedShard.clusterId + ) # nodes[0] is connected only to nodes[1] let connOk1 = await nodes[0].peerManager.connectRelay( @@ -321,26 +327,26 @@ suite "WakuNode2 - Validators": for j in 0 ..< 50: let unsignedMessage = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: now(), ephemeral: true, ) - discard await nodes[0].publish(some(spamProtectedTopic), unsignedMessage) + discard await nodes[0].publish(some($spamProtectedShard), unsignedMessage) # nodes[0] spams 50 wrongly signed messages (nodes[0] just knows of nodes[1]) for j in 0 ..< 50: var msg = WakuMessage( payload: urandom(1 * (10 ^ 3)), - contentTopic: spamProtectedTopic, + contentTopic: spamProtectedShard, version: 2, timestamp: now(), ephemeral: true, ) # Sign the message with a wrong key msg.meta = - wrongSecretKey.sign(SkMessage(spamProtectedTopic.msgHash(msg))).toRaw()[0 .. 63] - discard await nodes[0].publish(some(spamProtectedTopic), msg) + wrongSecretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[0 .. 63] + discard await nodes[0].publish(some($spamProtectedShard), msg) # Wait for gossip await sleepAsync(2.seconds) @@ -353,7 +359,7 @@ suite "WakuNode2 - Validators": # peer1 got invalid messages from peer0 let p0Id = nodes[0].peerInfo.peerId check: - nodes[1].wakuRelay.peerStats[p0Id].topicInfos[spamProtectedTopic].invalidMessageDeliveries == + nodes[1].wakuRelay.peerStats[p0Id].topicInfos[spamProtectedShard].invalidMessageDeliveries == 100.0 # peer1 did not gossip further, so no other node rx invalid messages @@ -362,7 +368,7 @@ suite "WakuNode2 - Validators": if k == p0Id and i == 1: continue check: - v.topicInfos[spamProtectedTopic].invalidMessageDeliveries == 0.0 + v.topicInfos[spamProtectedShard].invalidMessageDeliveries == 0.0 # Stop all nodes await allFutures(nodes.mapIt(it.stop())) diff --git a/tests/wakunode_rest/test_rest_relay.nim b/tests/wakunode_rest/test_rest_relay.nim index c8a372984a..9732d114b6 100644 --- a/tests/wakunode_rest/test_rest_relay.nim +++ b/tests/wakunode_rest/test_rest_relay.nim @@ -54,16 +54,16 @@ suite "Waku v2 Rest API - Relay": installRelayApiHandlers(restServer.router, node, cache) restServer.start() - let pubSubTopics = - @[ - PubSubTopic("pubsub-topic-1"), - PubSubTopic("pubsub-topic-2"), - PubSubTopic("pubsub-topic-3"), - ] + let + shard0 = RelayShard(clusterId: DefaultClusterId, shardId: 0) + shard1 = RelayShard(clusterId: DefaultClusterId, shardId: 1) + shard2 = RelayShard(clusterId: DefaultClusterId, shardId: 2) + + let shards = @[$shard0, $shard1, $shard2] # When let client = newRestHttpClient(initTAddress(restAddress, restPort)) - let response = await client.relayPostSubscriptionsV1(pubSubTopics) + let response = await client.relayPostSubscriptionsV1(shards) # Then check: @@ -72,12 +72,12 @@ suite "Waku v2 Rest API - Relay": response.data == "OK" check: - cache.isPubsubSubscribed("pubsub-topic-1") - cache.isPubsubSubscribed("pubsub-topic-2") - cache.isPubsubSubscribed("pubsub-topic-3") + cache.isPubsubSubscribed($shard0) + cache.isPubsubSubscribed($shard1) + cache.isPubsubSubscribed($shard2) check: - toSeq(node.wakuRelay.subscribedTopics).len == pubSubTopics.len + toSeq(node.wakuRelay.subscribedTopics).len == shards.len await restServer.stop() await restServer.closeWait() @@ -87,9 +87,15 @@ suite "Waku v2 Rest API - Relay": # Given let node = testWakuNode() await node.start() - await node.mountRelay( - @["pubsub-topic-1", "pubsub-topic-2", "pubsub-topic-3", "pubsub-topic-x"] - ) + + let + shard0 = RelayShard(clusterId: DefaultClusterId, shardId: 0) + shard1 = RelayShard(clusterId: DefaultClusterId, shardId: 1) + shard2 = RelayShard(clusterId: DefaultClusterId, shardId: 2) + shard3 = RelayShard(clusterId: DefaultClusterId, shardId: 3) + shard4 = RelayShard(clusterId: DefaultClusterId, shardId: 4) + + await node.mountRelay(@[shard0, shard1, shard2, shard3]) var restPort = Port(0) let restAddress = parseIpAddress("0.0.0.0") @@ -98,25 +104,19 @@ suite "Waku v2 Rest API - Relay": restPort = restServer.httpServer.address.port # update with bound port for client use let cache = MessageCache.init() - cache.pubsubSubscribe("pubsub-topic-1") - cache.pubsubSubscribe("pubsub-topic-2") - cache.pubsubSubscribe("pubsub-topic-3") - cache.pubsubSubscribe("pubsub-topic-x") + cache.pubsubSubscribe($shard0) + cache.pubsubSubscribe($shard1) + cache.pubsubSubscribe($shard2) + cache.pubsubSubscribe($shard3) installRelayApiHandlers(restServer.router, node, cache) restServer.start() - let pubSubTopics = - @[ - PubSubTopic("pubsub-topic-1"), - PubSubTopic("pubsub-topic-2"), - PubSubTopic("pubsub-topic-3"), - PubSubTopic("pubsub-topic-y"), - ] + let shards = @[$shard0, $shard1, $shard2, $shard4] # When let client = newRestHttpClient(initTAddress(restAddress, restPort)) - let response = await client.relayDeleteSubscriptionsV1(pubSubTopics) + let response = await client.relayDeleteSubscriptionsV1(shards) # Then check: @@ -125,16 +125,16 @@ suite "Waku v2 Rest API - Relay": response.data == "OK" check: - not cache.isPubsubSubscribed("pubsub-topic-1") - not node.wakuRelay.isSubscribed("pubsub-topic-1") - not cache.isPubsubSubscribed("pubsub-topic-2") - not node.wakuRelay.isSubscribed("pubsub-topic-2") - not cache.isPubsubSubscribed("pubsub-topic-3") - not node.wakuRelay.isSubscribed("pubsub-topic-3") - cache.isPubsubSubscribed("pubsub-topic-x") - node.wakuRelay.isSubscribed("pubsub-topic-x") - not cache.isPubsubSubscribed("pubsub-topic-y") - not node.wakuRelay.isSubscribed("pubsub-topic-y") + not cache.isPubsubSubscribed($shard0) + not node.wakuRelay.isSubscribed($shard0) + not cache.isPubsubSubscribed($shard1) + not node.wakuRelay.isSubscribed($shard1) + not cache.isPubsubSubscribed($shard2) + not node.wakuRelay.isSubscribed($shard2) + cache.isPubsubSubscribed($shard3) + node.wakuRelay.isSubscribed($shard3) + not cache.isPubsubSubscribed($shard4) + not node.wakuRelay.isSubscribed($shard4) await restServer.stop() await restServer.closeWait() diff --git a/tests/wakunode_rest/test_rest_store.nim b/tests/wakunode_rest/test_rest_store.nim index 32e8151db3..24248ee4b3 100644 --- a/tests/wakunode_rest/test_rest_store.nim +++ b/tests/wakunode_rest/test_rest_store.nim @@ -297,8 +297,7 @@ procSuite "Waku Rest API - Store v3": if reqHash.isSome(): reqHash.get().toRestStringWakuMessageHash() else: - "" - , # base64-encoded digest. Empty ignores the field. + "", # base64-encoded digest. Empty ignores the field. "true", # ascending "7", # page size. Empty implies default page size. ) @@ -790,8 +789,7 @@ procSuite "Waku Rest API - Store v3": if reqHash.isSome(): reqHash.get().toRestStringWakuMessageHash() else: - "" - , # base64-encoded digest. Empty ignores the field. + "", # base64-encoded digest. Empty ignores the field. "true", # ascending "3", # page size. Empty implies default page size. ) @@ -827,8 +825,7 @@ procSuite "Waku Rest API - Store v3": if reqHash.isSome(): reqHash.get().toRestStringWakuMessageHash() else: - "" - , # base64-encoded digest. Empty ignores the field. + "", # base64-encoded digest. Empty ignores the field. ) check: @@ -850,8 +847,7 @@ procSuite "Waku Rest API - Store v3": if reqHash.isSome(): reqHash.get().toRestStringWakuMessageHash() else: - "" - , # base64-encoded digest. Empty ignores the field. + "", # base64-encoded digest. Empty ignores the field. "true", # ascending "5", # page size. Empty implies default page size. ) diff --git a/vendor/negentropy b/vendor/negentropy new file mode 160000 index 0000000000..3c2df0b899 --- /dev/null +++ b/vendor/negentropy @@ -0,0 +1 @@ +Subproject commit 3c2df0b899bae1213d27563e44c5a0d610d8aada diff --git a/vendor/nim-chronicles b/vendor/nim-chronicles index 33761a5f77..a28bb9781c 160000 --- a/vendor/nim-chronicles +++ b/vendor/nim-chronicles @@ -1 +1 @@ -Subproject commit 33761a5f77610d3f87f774244490eae43a9ac5a1 +Subproject commit a28bb9781ce74e725796c307ad05083e646872be diff --git a/vendor/nim-chronos b/vendor/nim-chronos index 13d28a5b71..c04576d829 160000 --- a/vendor/nim-chronos +++ b/vendor/nim-chronos @@ -1 +1 @@ -Subproject commit 13d28a5b710c414be17bfe36ca25bf34771875cc +Subproject commit c04576d829b8a0a1b12baaa8bc92037501b3a4a0 diff --git a/vendor/nim-http-utils b/vendor/nim-http-utils index 98496aa24d..8b88ad6dd9 160000 --- a/vendor/nim-http-utils +++ b/vendor/nim-http-utils @@ -1 +1 @@ -Subproject commit 98496aa24d9364d1652e531f5f346de9b7cb3e15 +Subproject commit 8b88ad6dd9a6326c29f82067800c483d9410d873 diff --git a/vendor/nim-json-serialization b/vendor/nim-json-serialization index 89f7be1783..8a4ed98bbd 160000 --- a/vendor/nim-json-serialization +++ b/vendor/nim-json-serialization @@ -1 +1 @@ -Subproject commit 89f7be1783b2f828a95dea1496fdac3510532997 +Subproject commit 8a4ed98bbd0a9479df15af2fa31da38a586ea6d5 diff --git a/vendor/nim-libbacktrace b/vendor/nim-libbacktrace index 4db9cae5ac..b6e26f03c0 160000 --- a/vendor/nim-libbacktrace +++ b/vendor/nim-libbacktrace @@ -1 +1 @@ -Subproject commit 4db9cae5ac0225e3439f577f5c5cd67086232b3f +Subproject commit b6e26f03c091a8e3bba6adc06198fc3055bacc66 diff --git a/vendor/nim-libp2p b/vendor/nim-libp2p index 94d93cbf25..18a2e79ce2 160000 --- a/vendor/nim-libp2p +++ b/vendor/nim-libp2p @@ -1 +1 @@ -Subproject commit 94d93cbf2531296f0e636aa702b500b242eb4158 +Subproject commit 18a2e79ce209d2f21d8c5db7a41659cc39dbe0b4 diff --git a/vendor/nim-metrics b/vendor/nim-metrics index f068c85196..29bb7ba63c 160000 --- a/vendor/nim-metrics +++ b/vendor/nim-metrics @@ -1 +1 @@ -Subproject commit f068c85196cd4464e97d52ff0ea3d5ed8e59da1b +Subproject commit 29bb7ba63cd884770169891687595348a70cf166 diff --git a/vendor/nim-nat-traversal b/vendor/nim-nat-traversal index 459fc49687..a3aa0c5f9d 160000 --- a/vendor/nim-nat-traversal +++ b/vendor/nim-nat-traversal @@ -1 +1 @@ -Subproject commit 459fc4968799bde97592137f42d93e5069f06e73 +Subproject commit a3aa0c5f9d2a2870f1fd0f7a613d4fe025c84ab7 diff --git a/vendor/nim-results b/vendor/nim-results index e2adf66b8b..71d404b314 160000 --- a/vendor/nim-results +++ b/vendor/nim-results @@ -1 +1 @@ -Subproject commit e2adf66b8bc2f41606e8469a5f0a850d1e545b55 +Subproject commit 71d404b314479a6205bfd050f4fe5fe49cdafc69 diff --git a/vendor/nim-secp256k1 b/vendor/nim-secp256k1 index c1795d1fb6..4470f49bcd 160000 --- a/vendor/nim-secp256k1 +++ b/vendor/nim-secp256k1 @@ -1 +1 @@ -Subproject commit c1795d1fb64b6cfe932a8d977a123b55a562dc52 +Subproject commit 4470f49bcd6bcbfb59f0eeb67315ca9ddac0bdc0 diff --git a/vendor/nim-sqlite3-abi b/vendor/nim-sqlite3-abi index 362e1bd9f6..acd3c32743 160000 --- a/vendor/nim-sqlite3-abi +++ b/vendor/nim-sqlite3-abi @@ -1 +1 @@ -Subproject commit 362e1bd9f689ad9f5380d9d27f0705b3d4dfc7d3 +Subproject commit acd3c327433784226b412757bdb5455b5be04c55 diff --git a/vendor/nim-stew b/vendor/nim-stew index e00fea1f6e..d4634c5405 160000 --- a/vendor/nim-stew +++ b/vendor/nim-stew @@ -1 +1 @@ -Subproject commit e00fea1f6e14234adb10d9c117a70905ae31872b +Subproject commit d4634c5405ac188e7050d348332edb6c3b09a527 diff --git a/vendor/nim-stint b/vendor/nim-stint index 9a3348bd44..7c81df9adc 160000 --- a/vendor/nim-stint +++ b/vendor/nim-stint @@ -1 +1 @@ -Subproject commit 9a3348bd4499045fb211afeeaf2a54730060c081 +Subproject commit 7c81df9adc80088f46a4c2b8bf2a46c26fab057c diff --git a/vendor/nph b/vendor/nph new file mode 160000 index 0000000000..31bdced07d --- /dev/null +++ b/vendor/nph @@ -0,0 +1 @@ +Subproject commit 31bdced07d3dc3d254669bd94210101c701deeda diff --git a/waku.nimble b/waku.nimble index e4d88a323d..fccb6f6b11 100644 --- a/waku.nimble +++ b/waku.nimble @@ -63,12 +63,12 @@ proc buildLibrary(name: string, srcDir = "./", params = "", `type` = "static") = extra_params &= " " & paramStr(i) if `type` == "static": exec "nim c" & " --out:build/" & name & - ".a --threads:on --app:staticlib --opt:size --noMain --mm:refc --header " & + ".a --threads:on --app:staticlib --opt:size --noMain --mm:refc --header --undef:metrics " & extra_params & " " & srcDir & name & ".nim" else: exec "nim c" & " --out:build/" & name & - ".so --threads:on --app:lib --opt:size --noMain --mm:refc --header " & extra_params & - " " & srcDir & name & ".nim" + ".so --threads:on --app:lib --opt:size --noMain --mm:refc --header --undef:metrics " & + extra_params & " " & srcDir & name & ".nim" proc buildMobileAndroid(srcDir = ".", params = "") = let cpu = getEnv("CPU") diff --git a/waku/common/databases/db_postgres/dbconn.nim b/waku/common/databases/db_postgres/dbconn.nim index 16e9602f66..bc5da4ee6c 100644 --- a/waku/common/databases/db_postgres/dbconn.nim +++ b/waku/common/databases/db_postgres/dbconn.nim @@ -1,4 +1,11 @@ -import std/[times, strutils], results, chronos +import + std/[times, strutils, asyncnet, os, sequtils], + results, + chronos, + metrics, + re, + chronicles +import ./query_metrics include db_connector/db_postgres @@ -33,7 +40,20 @@ proc open*(connString: string): Result[DbConn, string] = return err("unknown reason") - ok(conn) + ## registering the socket fd in chronos for better wait for data + let asyncFd = cast[asyncengine.AsyncFD](pqsocket(conn)) + asyncengine.register(asyncFd) + + return ok(conn) + +proc closeDbConn*(db: DbConn) {.raises: [OSError].} = + let fd = db.pqsocket() + if fd != -1: + asyncengine.unregister(cast[asyncengine.AsyncFD](fd)) + db.close() + +proc `$`(self: SqlQuery): string = + return cast[string](self) proc sendQuery( db: DbConn, query: SqlQuery, args: seq[string] @@ -112,23 +132,17 @@ proc waitQueryToFinish( ## The 'rowCallback' param is != nil when the underlying query wants to retrieve results (SELECT.) ## For other queries, like "INSERT", 'rowCallback' should be nil. - while db.pqisBusy() == 1: - ## TODO: Enhance performance in concurrent queries. - ## The connection keeps busy for quite a long time when performing intense concurrect queries. - ## For example, a given query can last 11 milliseconds within from the database point of view - ## but, on the other hand, the connection remains in "db.pqisBusy() == 1" for 100ms more. - ## I think this is because `nwaku` is single-threaded and it has to handle many connections (20) - ## simultaneously. Therefore, there is an underlying resource sharing (cpu) that makes this - ## to happen. Notice that the _Postgres_ database spawns one process per each connection. - let success = db.pqconsumeInput() - - if success != 1: - db.check().isOkOr: - return err("failed pqconsumeInput: " & $error) + var dataAvailable = false + proc onDataAvailable(udata: pointer) {.gcsafe, raises: [].} = + dataAvailable = true + + let asyncFd = cast[asyncengine.AsyncFD](pqsocket(db)) - return err("failed pqconsumeInput: unknown reason") + asyncengine.addReader2(asyncFd, onDataAvailable).isOkOr: + return err("failed to add event reader in waitQueryToFinish: " & $error) - await sleepAsync(timer.milliseconds(0)) # Do not block the async runtime + while not dataAvailable: + await sleepAsync(timer.milliseconds(1)) ## Now retrieve the result while true: @@ -148,12 +162,37 @@ proc waitQueryToFinish( proc dbConnQuery*( db: DbConn, query: SqlQuery, args: seq[string], rowCallback: DataProc ): Future[Result[void, string]] {.async, gcsafe.} = + let cleanedQuery = ($query).replace(" ", "").replace("\n", "") + ## remove everything between ' or " all possible sequence of numbers. e.g. rm partition partition + var querySummary = cleanedQuery.replace(re"""(['"]).*?\1""", "") + querySummary = querySummary.replace(re"\d+", "") + querySummary = "query_tag_" & querySummary[0 ..< min(querySummary.len, 200)] + + var queryStartTime = getTime().toUnixFloat() + (await db.sendQuery(query, args)).isOkOr: return err("error in dbConnQuery calling sendQuery: " & $error) + let sendDuration = getTime().toUnixFloat() - queryStartTime + query_time_secs.set(sendDuration, [querySummary, "sendQuery"]) + + queryStartTime = getTime().toUnixFloat() + (await db.waitQueryToFinish(rowCallback)).isOkOr: return err("error in dbConnQuery calling waitQueryToFinish: " & $error) + let waitDuration = getTime().toUnixFloat() - queryStartTime + query_time_secs.set(waitDuration, [querySummary, "waitFinish"]) + + query_count.inc(labelValues = [querySummary]) + + if "insert" notin ($query).toLower(): + debug "dbConnQuery", + query = $query, + querySummary, + waitDurationSecs = waitDuration, + sendDurationSecs = sendDuration + return ok() proc dbConnQueryPrepared*( @@ -164,10 +203,25 @@ proc dbConnQueryPrepared*( paramFormats: seq[int32], rowCallback: DataProc, ): Future[Result[void, string]] {.async, gcsafe.} = + var queryStartTime = getTime().toUnixFloat() db.sendQueryPrepared(stmtName, paramValues, paramLengths, paramFormats).isOkOr: return err("error in dbConnQueryPrepared calling sendQuery: " & $error) + let sendDuration = getTime().toUnixFloat() - queryStartTime + query_time_secs.set(sendDuration, [stmtName, "sendQuery"]) + + queryStartTime = getTime().toUnixFloat() + (await db.waitQueryToFinish(rowCallback)).isOkOr: return err("error in dbConnQueryPrepared calling waitQueryToFinish: " & $error) + let waitDuration = getTime().toUnixFloat() - queryStartTime + query_time_secs.set(waitDuration, [stmtName, "waitFinish"]) + + query_count.inc(labelValues = [stmtName]) + + if "insert" notin stmtName.toLower(): + debug "dbConnQueryPrepared", + stmtName, waitDurationSecs = waitDuration, sendDurationSecs = sendDuration + return ok() diff --git a/waku/common/databases/db_postgres/pgasyncpool.nim b/waku/common/databases/db_postgres/pgasyncpool.nim index e9124f4fb1..66e66bd2ff 100644 --- a/waku/common/databases/db_postgres/pgasyncpool.nim +++ b/waku/common/databases/db_postgres/pgasyncpool.nim @@ -2,15 +2,15 @@ # Inspired by: https://github.com/treeform/pg/ {.push raises: [].} -import std/[sequtils, nre, strformat, sets], results, chronos -import ./dbconn, ../common +import std/[sequtils, nre, strformat, sets], results, chronos, chronicles +import ./dbconn, ../common, ../../../waku_core/time type PgAsyncPoolState {.pure.} = enum Closed Live Closing -type PgDbConn = object +type PgDbConn = ref object dbConn: DbConn open: bool busy: bool @@ -76,14 +76,11 @@ proc close*(pool: PgAsyncPool): Future[Result[void, string]] {.async.} = if pool.conns[i].busy: continue - if pool.conns[i].open: - pool.conns[i].dbConn.close() - pool.conns[i].busy = false - pool.conns[i].open = false - for i in 0 ..< pool.conns.len: if pool.conns[i].open: - pool.conns[i].dbConn.close() + pool.conns[i].dbConn.closeDbConn() + pool.conns[i].busy = false + pool.conns[i].open = false pool.conns.setLen(0) pool.state = PgAsyncPoolState.Closed @@ -149,18 +146,26 @@ proc releaseConn(pool: PgAsyncPool, conn: DbConn) = if pool.conns[i].dbConn == conn: pool.conns[i].busy = false +const SlowQueryThresholdInNanoSeconds = 2_000_000_000 + proc pgQuery*( pool: PgAsyncPool, query: string, args: seq[string] = newSeq[string](0), rowCallback: DataProc = nil, + requestId: string = "", ): Future[DatabaseResult[void]] {.async.} = let connIndex = (await pool.getConnIndex()).valueOr: return err("connRes.isErr in query: " & $error) + let queryStartTime = getNowInNanosecondTime() let conn = pool.conns[connIndex].dbConn defer: pool.releaseConn(conn) + let queryDuration = getNowInNanosecondTime() - queryStartTime + if queryDuration > SlowQueryThresholdInNanoSeconds: + debug "pgQuery slow query", + query_duration_secs = (queryDuration / 1_000_000_000), query, requestId (await conn.dbConnQuery(sql(query), args, rowCallback)).isOkOr: return err("error in asyncpool query: " & $error) @@ -175,6 +180,7 @@ proc runStmt*( paramLengths: seq[int32], paramFormats: seq[int32], rowCallback: DataProc = nil, + requestId: string = "", ): Future[DatabaseResult[void]] {.async.} = ## Runs a stored statement, for performance purposes. ## The stored statements are connection specific and is a technique of caching a very common @@ -187,8 +193,16 @@ proc runStmt*( return err("Error in runStmt: " & $error) let conn = pool.conns[connIndex].dbConn + let queryStartTime = getNowInNanosecondTime() + defer: pool.releaseConn(conn) + let queryDuration = getNowInNanosecondTime() - queryStartTime + if queryDuration > SlowQueryThresholdInNanoSeconds: + debug "runStmt slow query", + query_duration = queryDuration / 1_000_000_000, + query = stmtDefinition, + requestId if not pool.conns[connIndex].preparedStmts.contains(stmtName): # The connection doesn't have that statement yet. Let's create it. diff --git a/waku/common/databases/db_postgres/query_metrics.nim b/waku/common/databases/db_postgres/query_metrics.nim new file mode 100644 index 0000000000..06209cac0a --- /dev/null +++ b/waku/common/databases/db_postgres/query_metrics.nim @@ -0,0 +1,7 @@ +import metrics + +declarePublicGauge query_time_secs, + "query time measured in nanoseconds", labels = ["query", "phase"] + +declarePublicCounter query_count, + "number of times a query is being performed", labels = ["query"] diff --git a/waku/common/rate_limit/service_metrics.nim b/waku/common/rate_limit/service_metrics.nim index fe0bbf05c8..339bf7f38b 100644 --- a/waku/common/rate_limit/service_metrics.nim +++ b/waku/common/rate_limit/service_metrics.nim @@ -1,9 +1,19 @@ {.push raises: [].} -import metrics +import std/options +import metrics, setting + +declarePublicGauge waku_service_requests_limit, + "Applied rate limit of non-relay service", ["service"] declarePublicCounter waku_service_requests, "number of non-relay service requests received", ["service", "state"] declarePublicCounter waku_service_network_bytes, "total incoming traffic of specific waku services", labels = ["service", "direction"] + +proc setServiceLimitMetric*(service: string, limit: Option[RateLimitSetting]) = + if limit.isSome() and not limit.get().isUnlimited(): + waku_service_requests_limit.set( + limit.get().calculateLimitPerSecond(), labelValues = [service] + ) diff --git a/waku/common/rate_limit/setting.nim b/waku/common/rate_limit/setting.nim index 420be9f717..70f0ee7212 100644 --- a/waku/common/rate_limit/setting.nim +++ b/waku/common/rate_limit/setting.nim @@ -1,12 +1,34 @@ {.push raises: [].} -import chronos/timer +import chronos/timer, std/[tables, strutils, options], regex, results # Setting for TokenBucket defined as volume over period of time type RateLimitSetting* = tuple[volume: int, period: Duration] +type RateLimitedProtocol* = enum + GLOBAL + STOREV2 + STOREV3 + LIGHTPUSH + PEEREXCHG + FILTER + +type ProtocolRateLimitSettings* = Table[RateLimitedProtocol, RateLimitSetting] + # Set the default to switch off rate limiting for now let DefaultGlobalNonRelayRateLimit*: RateLimitSetting = (0, 0.minutes) +let UnlimitedRateLimit*: RateLimitSetting = (0, 0.seconds) + +# Acceptable call frequence from one peer using filter service +# Assumption is having to set up a subscription with max 30 calls than using ping in every min +# While subscribe/unsubscribe events are distributed in time among clients, pings will happen regularly from +# all subscribed peers +let FilterDefaultPerPeerRateLimit*: RateLimitSetting = (30, 1.minutes) + +# For being used under GC-safe condition must use threadvar +var DefaultProtocolRateLimit* {.threadvar.}: ProtocolRateLimitSettings +DefaultProtocolRateLimit = + {GLOBAL: UnlimitedRateLimit, FILTER: FilterDefaultPerPeerRateLimit}.toTable() proc isUnlimited*(t: RateLimitSetting): bool {.inline.} = return t.volume <= 0 or t.period <= 0.seconds @@ -17,3 +39,97 @@ func `$`*(t: RateLimitSetting): string {.inline.} = "no-limit" else: $t.volume & "/" & $t.period + +proc translate(sProtocol: string): RateLimitedProtocol {.raises: [ValueError].} = + if sProtocol.len == 0: + return GLOBAL + + case sProtocol + of "global": + return GLOBAL + of "storev2": + return STOREV2 + of "storev3": + return STOREV3 + of "lightpush": + return LIGHTPUSH + of "px": + return PEEREXCHG + of "filter": + return FILTER + else: + raise newException(ValueError, "Unknown protocol definition: " & sProtocol) + +proc fillSettingTable( + t: var ProtocolRateLimitSettings, sProtocol: var string, setting: RateLimitSetting +) {.raises: [ValueError].} = + if sProtocol == "store": + # generic store will only applies to version which is not listed directly + discard t.hasKeyOrPut(STOREV2, setting) + discard t.hasKeyOrPut(STOREV3, setting) + else: + let protocol = translate(sProtocol) + # always overrides, last one wins if same protocol duplicated + t[protocol] = setting + +proc parse*( + T: type ProtocolRateLimitSettings, settings: seq[string] +): Result[ProtocolRateLimitSettings, string] = + var settingsTable: ProtocolRateLimitSettings = + initTable[RateLimitedProtocol, RateLimitSetting]() + + ## Following regex can match the exact syntax of how rate limit can be set for different protocol or global. + ## It uses capture groups + ## group0: Will be check if protocol name is followed by a colon but only if protocol name is set. + ## group1: Protocol name, if empty we take it as "global" setting + ## group2: Volume of tokens - only integer + ## group3: Duration of period - only integer + ## group4: Unit of period - only h:hour, m:minute, s:second, ms:millisecond allowed + ## whitespaces are allowed lazily + const parseRegex = + """^\s*((store|storev2|storev3|lightpush|px|filter)\s*:)?\s*(\d+)\s*\/\s*(\d+)\s*(s|h|m|ms)\s*$""" + const regexParseSize = re2(parseRegex) + for settingStr in settings: + let aSetting = settingStr.toLower() + try: + var m: RegexMatch2 + if aSetting.match(regexParseSize, m) == false: + return err("Invalid rate-limit setting: " & settingStr) + + var sProtocol = aSetting[m.captures[1]] + let volume = aSetting[m.captures[2]].parseInt() + let duration = aSetting[m.captures[3]].parseInt() + let periodUnit = aSetting[m.captures[4]] + + var period = 0.seconds + case periodUnit + of "ms": + period = duration.milliseconds + of "s": + period = duration.seconds + of "m": + period = duration.minutes + of "h": + period = duration.hours + + fillSettingTable(settingsTable, sProtocol, (volume, period)) + except ValueError: + return err("Invalid rate-limit setting: " & settingStr) + + # If there were no global setting predefined, we set unlimited + # due it is taken for protocols not defined in the list - thus those will not apply accidentally wrong settings. + discard settingsTable.hasKeyOrPut(GLOBAL, UnlimitedRateLimit) + discard settingsTable.hasKeyOrPut(FILTER, FilterDefaultPerPeerRateLimit) + + return ok(settingsTable) + +proc getSetting*( + t: ProtocolRateLimitSettings, protocol: RateLimitedProtocol +): RateLimitSetting = + let default = t.getOrDefault(GLOBAL, UnlimitedRateLimit) + return t.getOrDefault(protocol, default) + +proc calculateLimitPerSecond*(setting: RateLimitSetting): float64 = + if setting.isUnlimited(): + return 0.float64 + return (setting.volume.float64 / setting.period.milliseconds.float64) * 1000.float64 diff --git a/waku/factory/builder.nim b/waku/factory/builder.nim index 1451a8a39b..7e203fe72b 100644 --- a/waku/factory/builder.nim +++ b/waku/factory/builder.nim @@ -8,7 +8,12 @@ import libp2p/builders, libp2p/nameresolving/nameresolver, libp2p/transports/wstransport -import ../waku_enr, ../discovery/waku_discv5, ../waku_node, ../node/peer_manager +import + ../waku_enr, + ../discovery/waku_discv5, + ../waku_node, + ../node/peer_manager, + ../common/rate_limit/setting type WakuNodeBuilder* = object # General @@ -34,6 +39,9 @@ type switchSslSecureCert: Option[string] switchSendSignedPeerRecord: Option[bool] + #Rate limit configs for non-relay req-resp protocols + rateLimitSettings: Option[seq[string]] + WakuNodeBuilderResult* = Result[void, string] ## Init @@ -105,6 +113,9 @@ proc withPeerManagerConfig*( proc withColocationLimit*(builder: var WakuNodeBuilder, colocationLimit: int) = builder.colocationLimit = colocationLimit +proc withRateLimit*(builder: var WakuNodeBuilder, limits: seq[string]) = + builder.rateLimitSettings = some(limits) + ## Waku switch proc withSwitchConfiguration*( @@ -184,4 +195,7 @@ proc build*(builder: WakuNodeBuilder): Result[WakuNode, string] = except Exception: return err("failed to build WakuNode instance: " & getCurrentExceptionMsg()) + if builder.rateLimitSettings.isSome(): + ?node.setRateLimits(builder.rateLimitSettings.get()) + ok(node) diff --git a/waku/factory/external_config.nim b/waku/factory/external_config.nim index c010af35f1..dcc71d54df 100644 --- a/waku/factory/external_config.nim +++ b/waku/factory/external_config.nim @@ -19,19 +19,24 @@ import ../common/confutils/envvar/std/net as confEnvvarNet, ../common/logging, ../waku_enr, - ../node/peer_manager + ../node/peer_manager, + ../waku_core/topics/pubsub_topic include ../waku_core/message/default_values export confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet +# Git version in git describe format (defined at compile time) +const git_version* {.strdefine.} = "n/a" + type ConfResult*[T] = Result[T, string] -type ProtectedTopic* = object - topic*: string - key*: secp256k1.SkPublicKey type EthRpcUrl* = distinct string +type ProtectedShard* = object + shard*: uint16 + key*: secp256k1.SkPublicKey + type StartUpCommand* = enum noCommand # default, runs waku generateRlnKeystore # generates a new RLN keystore @@ -134,10 +139,17 @@ type WakuNodeConf* = object ## Application-level configuration protectedTopics* {. desc: - "Topics and its public key to be used for message validation, topic:pubkey. Argument may be repeated.", - defaultValue: newSeq[ProtectedTopic](0), + "Deprecated. Topics and its public key to be used for message validation, topic:pubkey. Argument may be repeated.", + defaultValue: newSeq[ProtectedShard](0), name: "protected-topic" - .}: seq[ProtectedTopic] + .}: seq[ProtectedShard] + + protectedShards* {. + desc: + "Shards and its public keys to be used for message validation, shard:pubkey. Argument may be repeated.", + defaultValue: newSeq[ProtectedShard](0), + name: "protected-shard" + .}: seq[ProtectedShard] ## General node config clusterId* {. @@ -148,7 +160,7 @@ type WakuNodeConf* = object .}: uint16 agentString* {. - defaultValue: "nwaku", + defaultValue: "nwaku-" & git_version, desc: "Node agent string which is used as identifier in network", name: "agent-string" .}: string @@ -303,14 +315,23 @@ type WakuNodeConf* = object name: "keep-alive" .}: bool + # If numShardsInNetwork is not set, we use the number of shards configured as numShardsInNetwork + numShardsInNetwork* {. + desc: "Number of shards in the network", + defaultValue: 0, + name: "num-shards-in-network" + .}: uint32 + pubsubTopics* {. - desc: "Default pubsub topic to subscribe to. Argument may be repeated.", + desc: + "Deprecated. Default pubsub topic to subscribe to. Argument may be repeated.", defaultValue: @[], name: "pubsub-topic" .}: seq[string] shards* {. - desc: "Shards index to subscribe to [0..MAX_SHARDS-1]. Argument may be repeated.", + desc: + "Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.", defaultValue: @[ uint16(0), @@ -385,6 +406,40 @@ type WakuNodeConf* = object name: "store-resume" .}: bool + ## Sync config + storeSync* {. + desc: "Enable store sync protocol: true|false", + defaultValue: false, + name: "store-sync" + .}: bool + + storeSyncInterval* {. + desc: "Interval between store sync attempts. In seconds.", + defaultValue: 300, # 5 minutes + name: "store-sync-interval" + .}: int64 + + storeSyncRange* {. + desc: "Amount of time to sync. In seconds.", + defaultValue: 3600, # 1 hours + name: "store-sync-range" + .}: int64 + + storeSyncRelayJitter* {. + hidden, + desc: "Time offset to account for message propagation jitter. In seconds.", + defaultValue: 20, + name: "store-sync-relay-jitter" + .}: int64 + + storeSyncMaxPayloadSize* {. + hidden, + desc: + "Max size in bytes of the inner negentropy payload. Cannot be less than 5K, 0 is unlimited.", + defaultValue: 0, + name: "store-sync-max-payload-size" + .}: int64 + ## Filter config filter* {. desc: "Enable filter protocol: true|false", defaultValue: false, name: "filter" @@ -429,6 +484,15 @@ type WakuNodeConf* = object name: "lightpushnode" .}: string + ## Reliability config + reliabilityEnabled* {. + desc: + """Adds an extra effort in the delivery/reception of messages by leveraging store-v3 requests. +with the drawback of consuming some more bandwitdh.""", + defaultValue: false, + name: "reliability" + .}: bool + ## REST HTTP config rest* {. desc: "Enable Waku REST HTTP server: true|false", defaultValue: true, name: "rest" @@ -458,12 +522,6 @@ type WakuNodeConf* = object name: "rest-admin" .}: bool - restPrivate* {. - desc: "Enable access to REST HTTP Private API: true|false", - defaultValue: false, - name: "rest-private" - .}: bool - restAllowOrigin* {. desc: "Allow cross-origin requests from the specified origin." & @@ -622,21 +680,18 @@ type WakuNodeConf* = object name: "websocket-secure-cert-path" .}: string - ## Rate limitation config - ## Currently default to switch of rate limit until become official - requestRateLimit* {. + ## Rate limitation config, if not set, rate limit checks will not be performed + rateLimits* {. desc: - "Number of requests to serve by each service in the specified period. Set it to 0 for unlimited", - defaultValue: 0, - name: "request-rate-limit" - .}: int - - ## Currently default to switch of rate limit until become official - requestRatePeriod* {. - desc: "Period of request rate limitation in seconds. Set it to 0 for unlimited", - defaultValue: 0, - name: "request-rate-period" - .}: int64 + "Rate limit settings for different protocols." & + "Format: protocol:volume/period" & + " Where 'protocol' can be one of: if not defined it means a global setting" & + " 'volume' and period must be an integer value. " & + " 'unit' must be one of - hours, minutes, seconds, milliseconds respectively. " & + "Argument may be repeated.", + defaultValue: newSeq[string](0), + name: "rate-limit" + .}: seq[string] ## Parsing @@ -667,20 +722,36 @@ proc parseCmdArg*[T](_: type seq[T], s: string): seq[T] {.raises: [ValueError].} proc completeCmdArg*(T: type crypto.PrivateKey, val: string): seq[string] = return @[] -proc parseCmdArg*(T: type ProtectedTopic, p: string): T = +# TODO: Remove when removing protected-topic configuration +proc isNumber(x: string): bool = + try: + discard parseInt(x) + result = true + except ValueError: + result = false + +proc parseCmdArg*(T: type ProtectedShard, p: string): T = let elements = p.split(":") if elements.len != 2: raise newException( - ValueError, "Invalid format for protected topic expected topic:publickey" + ValueError, "Invalid format for protected shard expected shard:publickey" ) - let publicKey = secp256k1.SkPublicKey.fromHex(elements[1]) if publicKey.isErr: raise newException(ValueError, "Invalid public key") - return ProtectedTopic(topic: elements[0], key: publicKey.get()) + if isNumber(elements[0]): + return ProtectedShard(shard: uint16.parseCmdArg(elements[0]), key: publicKey.get()) -proc completeCmdArg*(T: type ProtectedTopic, val: string): seq[string] = + # TODO: Remove when removing protected-topic configuration + let shard = RelayShard.parse(elements[0]).valueOr: + raise newException( + ValueError, + "Invalid pubsub topic. Pubsub topics must be in the format /waku/2/rs//", + ) + return ProtectedShard(shard: shard.shardId, key: publicKey.get()) + +proc completeCmdArg*(T: type ProtectedShard, val: string): seq[string] = return @[] proc completeCmdArg*(T: type IpAddress, val: string): seq[string] = @@ -742,18 +813,18 @@ proc readValue*( raise newException(SerializationError, getCurrentExceptionMsg()) proc readValue*( - r: var TomlReader, value: var ProtectedTopic + r: var TomlReader, value: var ProtectedShard ) {.raises: [SerializationError].} = try: - value = parseCmdArg(ProtectedTopic, r.readValue(string)) + value = parseCmdArg(ProtectedShard, r.readValue(string)) except CatchableError: raise newException(SerializationError, getCurrentExceptionMsg()) proc readValue*( - r: var EnvvarReader, value: var ProtectedTopic + r: var EnvvarReader, value: var ProtectedShard ) {.raises: [SerializationError].} = try: - value = parseCmdArg(ProtectedTopic, r.readValue(string)) + value = parseCmdArg(ProtectedShard, r.readValue(string)) except CatchableError: raise newException(SerializationError, getCurrentExceptionMsg()) @@ -786,6 +857,7 @@ proc load*(T: type WakuNodeConf, version = ""): ConfResult[T] = sources.addConfigFile(Toml, conf.configFile.get()) , ) + ok(conf) except CatchableError: err(getCurrentExceptionMsg()) diff --git a/waku/factory/internal_config.nim b/waku/factory/internal_config.nim index c284982310..064a03acd9 100644 --- a/waku/factory/internal_config.nim +++ b/waku/factory/internal_config.nim @@ -28,29 +28,8 @@ proc enrConfiguration*( enrBuilder.withMultiaddrs(netConfig.enrMultiaddrs) - var shards = newSeq[uint16]() - - let shardsOpt = topicsToRelayShards(conf.pubsubTopics).valueOr: - error "failed to parse pubsub topic, please format according to static shard specification", - error = $error - return err("failed to parse pubsub topic: " & $error) - - if shardsOpt.isSome(): - let relayShards = shardsOpt.get() - - if relayShards.clusterid != conf.clusterId: - error "pubsub topic corresponds to different shard than configured", - nodeCluster = conf.clusterId, pubsubCluster = relayShards.clusterid - return err("pubsub topic corresponds to different shard than configured") - - shards = relayShards.shardIds - elif conf.shards.len > 0: - shards = toSeq(conf.shards.mapIt(uint16(it))) - else: - info "no pubsub topics specified" - enrBuilder.withWakuRelaySharding( - RelayShards(clusterId: conf.clusterId, shardIds: shards) + RelayShards(clusterId: conf.clusterId, shardIds: conf.shards) ).isOkOr: return err("could not initialize ENR with shards") @@ -142,6 +121,7 @@ proc networkConfiguration*(conf: WakuNodeConf, clientId: string): NetConfigResul filter = conf.filter, store = conf.store, relay = conf.relay, + sync = conf.storeSync, ) # Resolve and use DNS domain IP diff --git a/waku/factory/networks_config.nim b/waku/factory/networks_config.nim index ee3c4ef972..41678f5904 100644 --- a/waku/factory/networks_config.nim +++ b/waku/factory/networks_config.nim @@ -10,7 +10,7 @@ type ClusterConf* = object rlnRelayBandwidthThreshold*: int rlnEpochSizeSec*: uint64 rlnRelayUserMessageLimit*: uint64 - pubsubTopics*: seq[string] + numShardsInNetwork*: uint32 discv5Discovery*: bool discv5BootstrapNodes*: seq[string] @@ -28,11 +28,7 @@ proc TheWakuNetworkConf*(T: type ClusterConf): ClusterConf = rlnRelayBandwidthThreshold: 0, rlnEpochSizeSec: 600, rlnRelayUserMessageLimit: 100, - pubsubTopics: - @[ - "/waku/2/rs/1/0", "/waku/2/rs/1/1", "/waku/2/rs/1/2", "/waku/2/rs/1/3", - "/waku/2/rs/1/4", "/waku/2/rs/1/5", "/waku/2/rs/1/6", "/waku/2/rs/1/7", - ], + numShardsInNetwork: 8, discv5Discovery: true, discv5BootstrapNodes: @[ diff --git a/waku/factory/node_factory.nim b/waku/factory/node_factory.nim index ea5f6666c1..a6140ad143 100644 --- a/waku/factory/node_factory.nim +++ b/waku/factory/node_factory.nim @@ -102,6 +102,7 @@ proc initNode( builder.withPeerManagerConfig( maxRelayPeers = conf.maxRelayPeers, shardAware = conf.relayShardedPeerManagement ) + builder.withRateLimit(conf.rateLimits) node = ?builder.build().mapErr( @@ -113,6 +114,13 @@ proc initNode( ## Mount protocols +proc getNumShardsInNetwork*(conf: WakuNodeConf): uint32 = + if conf.numShardsInNetwork != 0: + return conf.numShardsInNetwork + # If conf.numShardsInNetwork is not set, use 1024 - the maximum possible as per the static sharding spec + # https://github.com/waku-org/specs/blob/master/standards/core/relay-sharding.md#static-sharding + return uint32(MaxShardIndex + 1) + proc setupProtocols( node: WakuNode, conf: WakuNodeConf, nodeKey: crypto.PrivateKey ): Future[Result[void, string]] {.async.} = @@ -127,7 +135,14 @@ proc setupProtocols( node.mountMetadata(conf.clusterId).isOkOr: return err("failed to mount waku metadata protocol: " & error) - node.mountSharding(conf.clusterId, uint32(conf.pubsubTopics.len)).isOkOr: + # If conf.numShardsInNetwork is not set, use the number of shards configured as numShardsInNetwork + let numShardsInNetwork = getNumShardsInNetwork(conf) + + if conf.numShardsInNetwork == 0: + warn "Number of shards in network not configured, setting it to", + numShardsInNetwork = $numShardsInNetwork + + node.mountSharding(conf.clusterId, numShardsInNetwork).isOkOr: return err("failed to mount waku sharding: " & error) # Mount relay on all nodes @@ -151,14 +166,20 @@ proc setupProtocols( peerExchangeHandler = some(handlePeerExchange) - let shards = - conf.contentTopics.mapIt(node.wakuSharding.getShard(it).expect("Valid Shard")) + var autoShards: seq[RelayShard] + for contentTopic in conf.contentTopics: + let shard = node.wakuSharding.getShard(contentTopic).valueOr: + return err("Could not parse content topic: " & error) + autoShards.add(shard) + debug "Shards created from content topics", - contentTopics = conf.contentTopics, shards = shards + contentTopics = conf.contentTopics, shards = autoShards - if conf.relay: - let pubsubTopics = conf.pubsubTopics & shards + let confShards = + conf.shards.mapIt(RelayShard(clusterId: conf.clusterId, shardId: uint16(it))) + let shards = confShards & autoShards + if conf.relay: let parsedMaxMsgSize = parseMsgSize(conf.maxMessageSize).valueOr: return err("failed to parse 'max-num-bytes-msg-size' param: " & $error) @@ -166,25 +187,22 @@ proc setupProtocols( try: await mountRelay( - node, - pubsubTopics, - peerExchangeHandler = peerExchangeHandler, - int(parsedMaxMsgSize), + node, shards, peerExchangeHandler = peerExchangeHandler, int(parsedMaxMsgSize) ) except CatchableError: return err("failed to mount waku relay protocol: " & getCurrentExceptionMsg()) # Add validation keys to protected topics - var subscribedProtectedTopics: seq[ProtectedTopic] - for topicKey in conf.protectedTopics: - if topicKey.topic notin pubsubTopics: - warn "protected topic not in subscribed pubsub topics, skipping adding validator", - protectedTopic = topicKey.topic, subscribedTopics = pubsubTopics + var subscribedProtectedShards: seq[ProtectedShard] + for shardKey in conf.protectedShards: + if shardKey.shard notin conf.shards: + warn "protected shard not in subscribed shards, skipping adding validator", + protectedShard = shardKey.shard, subscribedShards = shards continue - subscribedProtectedTopics.add(topicKey) + subscribedProtectedShards.add(shardKey) notice "routing only signed traffic", - protectedTopic = topicKey.topic, publicKey = topicKey.key - node.wakuRelay.addSignedTopicsValidator(subscribedProtectedTopics) + protectedShard = shardKey.shard, publicKey = shardKey.key + node.wakuRelay.addSignedShardsValidator(subscribedProtectedShards, conf.clusterId) # Only relay nodes can be rendezvous points. if conf.rendezvous: @@ -258,20 +276,17 @@ proc setupProtocols( if mountArcRes.isErr(): return err("failed to mount waku archive protocol: " & mountArcRes.error) - let rateLimitSetting: RateLimitSetting = - (conf.requestRateLimit, chronos.seconds(conf.requestRatePeriod)) - if conf.legacyStore: # Store legacy setup try: - await mountLegacyStore(node, rateLimitSetting) + await mountLegacyStore(node, node.rateLimitSettings.getSetting(STOREV2)) except CatchableError: return err("failed to mount waku legacy store protocol: " & getCurrentExceptionMsg()) # Store setup try: - await mountStore(node, rateLimitSetting) + await mountStore(node, node.rateLimitSettings.getSetting(STOREV3)) except CatchableError: return err("failed to mount waku store protocol: " & getCurrentExceptionMsg()) @@ -296,12 +311,21 @@ proc setupProtocols( if conf.store and conf.storeResume: node.setupStoreResume() + if conf.storeSync: + ( + await node.mountWakuSync( + int(conf.storeSyncMaxPayloadSize), + conf.storeSyncRange.seconds(), + conf.storeSyncInterval.seconds(), + conf.storeSyncRelayJitter.seconds(), + ) + ).isOkOr: + return err("failed to mount waku sync protocol: " & $error) + # NOTE Must be mounted after relay if conf.lightpush: try: - let rateLimitSetting: RateLimitSetting = - (conf.requestRateLimit, chronos.seconds(conf.requestRatePeriod)) - await mountLightPush(node, rateLimitSetting) + await mountLightPush(node, node.rateLimitSettings.getSetting(LIGHTPUSH)) except CatchableError: return err("failed to mount waku lightpush protocol: " & getCurrentExceptionMsg()) @@ -321,6 +345,7 @@ proc setupProtocols( subscriptionTimeout = chronos.seconds(conf.filterSubscriptionTimeout), maxFilterPeers = conf.filterMaxPeersToServe, maxFilterCriteriaPerPeer = conf.filterMaxCriteria, + rateLimitSetting = node.rateLimitSettings.getSetting(FILTER), ) except CatchableError: return err("failed to mount waku filter protocol: " & getCurrentExceptionMsg()) @@ -341,7 +366,9 @@ proc setupProtocols( # waku peer exchange setup if conf.peerExchange: try: - await mountPeerExchange(node, some(conf.clusterId)) + await mountPeerExchange( + node, some(conf.clusterId), node.rateLimitSettings.getSetting(PEEREXCHG) + ) except CatchableError: return err("failed to mount waku peer-exchange protocol: " & getCurrentExceptionMsg()) diff --git a/waku/factory/validator_signed.nim b/waku/factory/validator_signed.nim index f4a9253adb..59ee384b1b 100644 --- a/waku/factory/validator_signed.nim +++ b/waku/factory/validator_signed.nim @@ -50,30 +50,34 @@ proc withinTimeWindow*(msg: WakuMessage): bool = return true return false -proc addSignedTopicsValidator*(w: WakuRelay, protectedTopics: seq[ProtectedTopic]) = - debug "adding validator to signed topics" +proc addSignedShardsValidator*( + w: WakuRelay, protectedShards: seq[ProtectedShard], clusterId: uint16 +) = + debug "adding validator to signed shards", protectedShards, clusterId proc validator( topic: string, msg: WakuMessage ): Future[errors.ValidationResult] {.async.} = var outcome = errors.ValidationResult.Reject - for protectedTopic in protectedTopics: - if (protectedTopic.topic == topic): + for protectedShard in protectedShards: + let topicString = + $RelayShard(clusterId: clusterId, shardId: uint16(protectedShard.shard)) + if (topicString == topic): if msg.timestamp != 0: if msg.withinTimeWindow(): let msgHash = SkMessage(topic.msgHash(msg)) let recoveredSignature = SkSignature.fromRaw(msg.meta) if recoveredSignature.isOk(): - if recoveredSignature.get.verify(msgHash, protectedTopic.key): + if recoveredSignature.get.verify(msgHash, protectedShard.key): outcome = errors.ValidationResult.Accept if outcome != errors.ValidationResult.Accept: debug "signed topic validation failed", - topic = topic, publicTopicKey = protectedTopic.key + topic = topic, publicShardKey = protectedShard.key waku_msg_validator_signed_outcome.inc(labelValues = [$outcome]) return outcome return errors.ValidationResult.Accept - w.addValidator(validator, "signed topic validation failed") + w.addValidator(validator, "signed shard validation failed") diff --git a/waku/factory/waku.nim b/waku/factory/waku.nim index c77ea52373..99b6ef479a 100644 --- a/waku/factory/waku.nim +++ b/waku/factory/waku.nim @@ -20,6 +20,7 @@ import ../waku_node, ../node/peer_manager, ../node/health_monitor, + ../node/delivery_monitor/delivery_monitor, ../waku_api/message_cache, ../waku_api/rest/server, ../waku_archive, @@ -51,6 +52,8 @@ type Waku* = object node*: WakuNode + deliveryMonitor: DeliveryMonitor + restServer*: WakuRestServerRef metricsServer*: MetricsHttpServerRef @@ -65,7 +68,7 @@ proc logConfig(conf: WakuNodeConf) = info "Configuration. Network", cluster = conf.clusterId, maxPeers = conf.maxRelayPeers - for shard in conf.pubsubTopics: + for shard in conf.shards: info "Configuration. Shards", shard = shard for i in conf.discv5BootstrapNodes: @@ -83,24 +86,55 @@ proc logConfig(conf: WakuNodeConf) = func version*(waku: Waku): string = waku.version +proc validateShards(conf: WakuNodeConf): Result[void, string] = + let numShardsInNetwork = getNumShardsInNetwork(conf) + + for shard in conf.shards: + if shard >= numShardsInNetwork: + let msg = + "validateShards invalid shard: " & $shard & " when numShardsInNetwork: " & + $numShardsInNetwork # fmt doesn't work + error "validateShards failed", error = msg + return err(msg) + + return ok() + ## Initialisation -proc init*(T: type Waku, conf: WakuNodeConf): Result[Waku, string] = - var confCopy = conf +proc init*(T: type Waku, confCopy: var WakuNodeConf): Result[Waku, string] = let rng = crypto.newRng() - logging.setupLog(conf.logLevel, conf.logFormat) + logging.setupLog(confCopy.logLevel, confCopy.logFormat) + + # TODO: remove after pubsubtopic config gets removed + var shards = newSeq[uint16]() + if confCopy.pubsubTopics.len > 0: + let shardsRes = topicsToRelayShards(confCopy.pubsubTopics) + if shardsRes.isErr(): + error "failed to parse pubsub topic, please format according to static shard specification", + error = shardsRes.error + return err("failed to parse pubsub topic: " & $shardsRes.error) + + let shardsOpt = shardsRes.get() + + if shardsOpt.isSome(): + let relayShards = shardsOpt.get() + if relayShards.clusterId != confCopy.clusterId: + error "clusterId of the pubsub topic should match the node's cluster. e.g. --pubsub-topic=/waku/2/rs/22/1 and --cluster-id=22", + nodeCluster = confCopy.clusterId, pubsubCluster = relayShards.clusterId + return err( + "clusterId of the pubsub topic should match the node's cluster. e.g. --pubsub-topic=/waku/2/rs/22/1 and --cluster-id=22" + ) + + for shard in relayShards.shardIds: + shards.add(shard) + confCopy.shards = shards case confCopy.clusterId # cluster-id=1 (aka The Waku Network) of 1: let twnClusterConf = ClusterConf.TheWakuNetworkConf() - if len(confCopy.shards) != 0: - confCopy.pubsubTopics = - confCopy.shards.mapIt(twnClusterConf.pubsubTopics[it.uint16]) - else: - confCopy.pubsubTopics = twnClusterConf.pubsubTopics # Override configuration confCopy.maxMessageSize = twnClusterConf.maxMessageSize @@ -114,6 +148,7 @@ proc init*(T: type Waku, conf: WakuNodeConf): Result[Waku, string] = confCopy.discv5BootstrapNodes & twnClusterConf.discv5BootstrapNodes confCopy.rlnEpochSizeSec = twnClusterConf.rlnEpochSizeSec confCopy.rlnRelayUserMessageLimit = twnClusterConf.rlnRelayUserMessageLimit + confCopy.numShardsInNetwork = twnClusterConf.numShardsInNetwork # Only set rlnRelay to true if relay is configured if confCopy.relay: @@ -124,6 +159,11 @@ proc init*(T: type Waku, conf: WakuNodeConf): Result[Waku, string] = info "Running nwaku node", version = git_version logConfig(confCopy) + let validateShardsRes = validateShards(confCopy) + if validateShardsRes.isErr(): + error "Failed validating shards", error = $validateShardsRes.error + return err("Failed validating shards: " & $validateShardsRes.error) + if not confCopy.nodekey.isSome(): let keyRes = crypto.PrivateKey.random(Secp256k1, rng[]) if keyRes.isErr(): @@ -147,13 +187,29 @@ proc init*(T: type Waku, conf: WakuNodeConf): Result[Waku, string] = error "Failed setting up node", error = nodeRes.error return err("Failed setting up node: " & nodeRes.error) + let node = nodeRes.get() + + var deliveryMonitor: DeliveryMonitor + if confCopy.reliabilityEnabled: + if confCopy.storenode == "": + return err("A storenode should be set when reliability mode is on") + + let deliveryMonitorRes = DeliveryMonitor.new( + node.wakuStoreClient, node.wakuRelay, node.wakuLightpushClient, + node.wakuFilterClient, + ) + if deliveryMonitorRes.isErr(): + return err("could not create delivery monitor: " & $deliveryMonitorRes.error) + deliveryMonitor = deliveryMonitorRes.get() + var waku = Waku( version: git_version, conf: confCopy, rng: rng, key: confCopy.nodekey.get(), - node: nodeRes.get(), + node: node, dynamicBootstrapNodes: dynamicBootstrapNodesRes.get(), + deliveryMonitor: deliveryMonitor, ) ok(waku) @@ -237,6 +293,10 @@ proc startWaku*(waku: ptr Waku): Future[Result[void, string]] {.async: (raises: (await waku.wakuDiscV5.start()).isOkOr: return err("failed to start waku discovery v5: " & $error) + ## Reliability + if not waku[].deliveryMonitor.isNil(): + waku[].deliveryMonitor.startDeliveryMonitor() + return ok() # Waku shutdown diff --git a/waku/node/delivery_monitor/delivery_callback.nim b/waku/node/delivery_monitor/delivery_callback.nim new file mode 100644 index 0000000000..c996bc7b0a --- /dev/null +++ b/waku/node/delivery_monitor/delivery_callback.nim @@ -0,0 +1,17 @@ +import ../../waku_core + +type DeliveryDirection* {.pure.} = enum + PUBLISHING + RECEIVING + +type DeliverySuccess* {.pure.} = enum + SUCCESSFUL + UNSUCCESSFUL + +type DeliveryFeedbackCallback* = proc( + success: DeliverySuccess, + dir: DeliveryDirection, + comment: string, + msgHash: WakuMessageHash, + msg: WakuMessage, +) {.gcsafe, raises: [].} diff --git a/waku/node/delivery_monitor/delivery_monitor.nim b/waku/node/delivery_monitor/delivery_monitor.nim new file mode 100644 index 0000000000..28f9e2507a --- /dev/null +++ b/waku/node/delivery_monitor/delivery_monitor.nim @@ -0,0 +1,43 @@ +## This module helps to ensure the correct transmission and reception of messages + +import results +import chronos +import + ./recv_monitor, + ./send_monitor, + ./delivery_callback, + ../../waku_core, + ../../waku_store/client, + ../../waku_relay/protocol, + ../../waku_lightpush/client, + ../../waku_filter_v2/client + +type DeliveryMonitor* = ref object + sendMonitor: SendMonitor + recvMonitor: RecvMonitor + +proc new*( + T: type DeliveryMonitor, + storeClient: WakuStoreClient, + wakuRelay: protocol.WakuRelay, + wakuLightpushClient: WakuLightPushClient, + wakuFilterClient: WakuFilterClient, +): Result[T, string] = + ## storeClient is needed to give store visitility to DeliveryMonitor + ## wakuRelay and wakuLightpushClient are needed to give a mechanism to SendMonitor to re-publish + let sendMonitor = ?SendMonitor.new(storeClient, wakuRelay, wakuLightpushClient) + let recvMonitor = RecvMonitor.new(storeClient, wakuFilterClient) + return ok(DeliveryMonitor(sendMonitor: sendMonitor, recvMonitor: recvMonitor)) + +proc startDeliveryMonitor*(self: DeliveryMonitor) = + self.sendMonitor.startSendMonitor() + self.recvMonitor.startRecvMonitor() + +proc stopDeliveryMonitor*(self: DeliveryMonitor) {.async.} = + self.sendMonitor.stopSendMonitor() + await self.recvMonitor.stopRecvMonitor() + +proc setDeliveryCallback*(self: DeliveryMonitor, deliveryCb: DeliveryFeedbackCallback) = + ## The deliveryCb is a proc defined by the api client so that it can get delivery feedback + self.sendMonitor.setDeliveryCallback(deliveryCb) + self.recvMonitor.setDeliveryCallback(deliveryCb) diff --git a/waku/node/delivery_monitor/not_delivered_storage/migrations.nim b/waku/node/delivery_monitor/not_delivered_storage/migrations.nim new file mode 100644 index 0000000000..66fb8587c7 --- /dev/null +++ b/waku/node/delivery_monitor/not_delivered_storage/migrations.nim @@ -0,0 +1,26 @@ +{.push raises: [].} + +import std/[tables, strutils, os], results, chronicles +import ../../../common/databases/db_sqlite, ../../../common/databases/common + +logScope: + topics = "waku node delivery_monitor" + +const TargetSchemaVersion* = 1 + # increase this when there is an update in the database schema + +template projectRoot(): string = + currentSourcePath.rsplit(DirSep, 1)[0] / ".." / ".." / ".." / ".." + +const PeerStoreMigrationPath: string = projectRoot / "migrations" / "sent_msgs" + +proc migrate*(db: SqliteDatabase): DatabaseResult[void] = + debug "starting peer store's sqlite database migration for sent messages" + + let migrationRes = + migrate(db, TargetSchemaVersion, migrationsScriptsDir = PeerStoreMigrationPath) + if migrationRes.isErr(): + return err("failed to execute migration scripts: " & migrationRes.error) + + debug "finished peer store's sqlite database migration for sent messages" + ok() diff --git a/waku/node/delivery_monitor/not_delivered_storage/not_delivered_storage.nim b/waku/node/delivery_monitor/not_delivered_storage/not_delivered_storage.nim new file mode 100644 index 0000000000..85611310bb --- /dev/null +++ b/waku/node/delivery_monitor/not_delivered_storage/not_delivered_storage.nim @@ -0,0 +1,38 @@ +## This module is aimed to keep track of the sent/published messages that are considered +## not being properly delivered. +## +## The archiving of such messages will happen in a local sqlite database. +## +## In the very first approach, we consider that a message is sent properly is it has been +## received by any store node. +## + +import results +import + ../../../common/databases/db_sqlite, + ../../../waku_core/message/message, + ../../../node/delivery_monitor/not_delivered_storage/migrations + +const NotDeliveredMessagesDbUrl = "not-delivered-messages.db" + +type NotDeliveredStorage* = ref object + database: SqliteDatabase + +type TrackedWakuMessage = object + msg: WakuMessage + numTrials: uint + ## for statistics purposes. Counts the number of times the node has tried to publish it + +proc new*(T: type NotDeliveredStorage): Result[T, string] = + let db = ?SqliteDatabase.new(NotDeliveredMessagesDbUrl) + + ?migrate(db) + + return ok(NotDeliveredStorage(database: db)) + +proc archiveMessage*( + self: NotDeliveredStorage, msg: WakuMessage +): Result[void, string] = + ## Archives a waku message so that we can keep track of it + ## even when the app restarts + return ok() diff --git a/waku/node/delivery_monitor/publish_observer.nim b/waku/node/delivery_monitor/publish_observer.nim new file mode 100644 index 0000000000..1f517f8bde --- /dev/null +++ b/waku/node/delivery_monitor/publish_observer.nim @@ -0,0 +1,9 @@ +import chronicles +import ../../waku_core/message/message + +type PublishObserver* = ref object of RootObj + +method onMessagePublished*( + self: PublishObserver, pubsubTopic: string, message: WakuMessage +) {.base, gcsafe, raises: [].} = + error "onMessagePublished not implemented" diff --git a/waku/node/delivery_monitor/recv_monitor.nim b/waku/node/delivery_monitor/recv_monitor.nim new file mode 100644 index 0000000000..3f82ddcd2e --- /dev/null +++ b/waku/node/delivery_monitor/recv_monitor.nim @@ -0,0 +1,196 @@ +## This module is in charge of taking care of the messages that this node is expecting to +## receive and is backed by store-v3 requests to get an additional degree of certainty +## + +import std/[tables, sequtils, sets, options] +import chronos, chronicles, libp2p/utility +import + ../../waku_core, + ./delivery_callback, + ./subscriptions_observer, + ../../waku_store/[client, common], + ../../waku_filter_v2/client, + ../../waku_core/topics + +const StoreCheckPeriod = chronos.minutes(5) ## How often to perform store queries + +const MaxMessageLife = chronos.minutes(7) ## Max time we will keep track of rx messages + +const PruneOldMsgsPeriod = chronos.minutes(1) + +const DelayExtra* = chronos.seconds(5) + ## Additional security time to overlap the missing messages queries + +type TupleHashAndMsg = tuple[hash: WakuMessageHash, msg: WakuMessage] + +type RecvMessage = object + msgHash: WakuMessageHash + rxTime: Timestamp + ## timestamp of the rx message. We will not keep the rx messages forever + +type RecvMonitor* = ref object of SubscriptionObserver + topicsInterest: Table[PubsubTopic, seq[ContentTopic]] + ## Tracks message verification requests and when was the last time a + ## pubsub topic was verified for missing messages + ## The key contains pubsub-topics + + storeClient: WakuStoreClient + deliveryCb: DeliveryFeedbackCallback + + recentReceivedMsgs: seq[RecvMessage] + + msgCheckerHandler: Future[void] ## allows to stop the msgChecker async task + msgPrunerHandler: Future[void] ## removes too old messages + + startTimeToCheck: Timestamp + endTimeToCheck: Timestamp + +proc getMissingMsgsFromStore( + self: RecvMonitor, msgHashes: seq[WakuMessageHash] +): Future[Result[seq[TupleHashAndMsg], string]] {.async.} = + let storeResp: StoreQueryResponse = ( + await self.storeClient.queryToAny( + StoreQueryRequest(includeData: true, messageHashes: msgHashes) + ) + ).valueOr: + return err("getMissingMsgsFromStore: " & $error) + + let otherwiseMsg = WakuMessage() + ## message to be returned if the Option message is none + return ok( + storeResp.messages.mapIt((hash: it.messageHash, msg: it.message.get(otherwiseMsg))) + ) + +proc performDeliveryFeedback( + self: RecvMonitor, + success: DeliverySuccess, + dir: DeliveryDirection, + comment: string, + msgHash: WakuMessageHash, + msg: WakuMessage, +) {.gcsafe, raises: [].} = + ## This procs allows to bring delivery feedback to the API client + ## It requires a 'deliveryCb' to be registered beforehand. + if self.deliveryCb.isNil(): + error "deliveryCb is nil in performDeliveryFeedback", + success, dir, comment, msg_hash + return + + debug "recv monitor performDeliveryFeedback", + success, dir, comment, msg_hash = shortLog(msgHash) + self.deliveryCb(success, dir, comment, msgHash, msg) + +proc msgChecker(self: RecvMonitor) {.async.} = + ## Continuously checks if a message has been received + while true: + await sleepAsync(StoreCheckPeriod) + + self.endTimeToCheck = getNowInNanosecondTime() + + var msgHashesInStore = newSeq[WakuMessageHash](0) + for pubsubTopic, cTopics in self.topicsInterest.pairs: + let storeResp: StoreQueryResponse = ( + await self.storeClient.queryToAny( + StoreQueryRequest( + includeData: false, + pubsubTopic: some(PubsubTopic(pubsubTopic)), + contentTopics: cTopics, + startTime: some(self.startTimeToCheck - DelayExtra.nanos), + endTime: some(self.endTimeToCheck + DelayExtra.nanos), + ) + ) + ).valueOr: + error "msgChecker failed to get remote msgHashes", + pubsubTopic, cTopics, error = $error + continue + + msgHashesInStore.add(storeResp.messages.mapIt(it.messageHash)) + + ## compare the msgHashes seen from the store vs the ones received directly + let rxMsgHashes = self.recentReceivedMsgs.mapIt(it.msgHash) + let missedHashes: seq[WakuMessageHash] = + msgHashesInStore.filterIt(not rxMsgHashes.contains(it)) + + ## Now retrieve the missed WakuMessages + let missingMsgsRet = await self.getMissingMsgsFromStore(missedHashes) + if missingMsgsRet.isOk(): + ## Give feedback so that the api client can perfom any action with the missed messages + for msgTuple in missingMsgsRet.get(): + self.performDeliveryFeedback( + DeliverySuccess.UNSUCCESSFUL, RECEIVING, "Missed message", msgTuple.hash, + msgTuple.msg, + ) + else: + error "failed to retrieve missing messages: ", error = $missingMsgsRet.error + + ## update next check times + self.startTimeToCheck = self.endTimeToCheck + +method onSubscribe( + self: RecvMonitor, pubsubTopic: string, contentTopics: seq[string] +) {.gcsafe, raises: [].} = + debug "onSubscribe", pubsubTopic, contentTopics + self.topicsInterest.withValue(pubsubTopic, contentTopicsOfInterest): + contentTopicsOfInterest[].add(contentTopics) + do: + self.topicsInterest[pubsubTopic] = contentTopics + +method onUnsubscribe( + self: RecvMonitor, pubsubTopic: string, contentTopics: seq[string] +) {.gcsafe, raises: [].} = + debug "onUnsubscribe", pubsubTopic, contentTopics + + self.topicsInterest.withValue(pubsubTopic, contentTopicsOfInterest): + let remainingCTopics = + contentTopicsOfInterest[].filterIt(not contentTopics.contains(it)) + contentTopicsOfInterest[] = remainingCTopics + + if remainingCTopics.len == 0: + self.topicsInterest.del(pubsubTopic) + do: + error "onUnsubscribe unsubscribing from wrong topic", pubsubTopic, contentTopics + +proc new*( + T: type RecvMonitor, + storeClient: WakuStoreClient, + wakuFilterClient: WakuFilterClient, +): T = + ## The storeClient will help to acquire any possible missed messages + + let now = getNowInNanosecondTime() + var recvMonitor = RecvMonitor(storeClient: storeClient, startTimeToCheck: now) + + if not wakuFilterClient.isNil(): + wakuFilterClient.addSubscrObserver(recvMonitor) + + let filterPushHandler = proc( + pubsubTopic: PubsubTopic, message: WakuMessage + ) {.async, closure.} = + ## Captures all the messages recived through filter + + let msgHash = computeMessageHash(pubSubTopic, message) + let rxMsg = RecvMessage(msgHash: msgHash, rxTime: message.timestamp) + recvMonitor.recentReceivedMsgs.add(rxMsg) + + wakuFilterClient.registerPushHandler(filterPushHandler) + + return recvMonitor + +proc loopPruneOldMessages(self: RecvMonitor) {.async.} = + while true: + let oldestAllowedTime = getNowInNanosecondTime() - MaxMessageLife.nanos + self.recentReceivedMsgs.keepItIf(it.rxTime > oldestAllowedTime) + await sleepAsync(PruneOldMsgsPeriod) + +proc startRecvMonitor*(self: RecvMonitor) = + self.msgCheckerHandler = self.msgChecker() + self.msgPrunerHandler = self.loopPruneOldMessages() + +proc stopRecvMonitor*(self: RecvMonitor) {.async.} = + if not self.msgCheckerHandler.isNil(): + await self.msgCheckerHandler.cancelAndWait() + if not self.msgPrunerHandler.isNil(): + await self.msgPrunerHandler.cancelAndWait() + +proc setDeliveryCallback*(self: RecvMonitor, deliveryCb: DeliveryFeedbackCallback) = + self.deliveryCb = deliveryCb diff --git a/waku/node/delivery_monitor/send_monitor.nim b/waku/node/delivery_monitor/send_monitor.nim new file mode 100644 index 0000000000..ce1ccf0cc9 --- /dev/null +++ b/waku/node/delivery_monitor/send_monitor.nim @@ -0,0 +1,212 @@ +## This module reinforces the publish operation with regular store-v3 requests. +## + +import std/[sets, sequtils] +import chronos, chronicles, libp2p/utility +import + ./delivery_callback, + ./publish_observer, + ../../waku_core, + ./not_delivered_storage/not_delivered_storage, + ../../waku_store/[client, common], + ../../waku_archive/archive, + ../../waku_relay/protocol, + ../../waku_lightpush/client + +const MaxTimeInCache* = chronos.minutes(1) + ## Messages older than this time will get completely forgotten on publication and a + ## feedback will be given when that happens + +const SendCheckInterval* = chronos.seconds(3) + ## Interval at which we check that messages have been properly received by a store node + +const MaxMessagesToCheckAtOnce = 100 + ## Max number of messages to check if they were properly archived by a store node + +const ArchiveTime = chronos.seconds(3) + ## Estimation of the time we wait until we start confirming that a message has been properly + ## received and archived by a store node + +type DeliveryInfo = object + pubsubTopic: string + msg: WakuMessage + +type SendMonitor* = ref object of PublishObserver + publishedMessages: Table[WakuMessageHash, DeliveryInfo] + ## Cache that contains the delivery info per message hash. + ## This is needed to make sure the published messages are properly published + + msgStoredCheckerHandle: Future[void] ## handle that allows to stop the async task + + notDeliveredStorage: NotDeliveredStorage + ## NOTE: this is not fully used because that might be tackled by higher abstraction layers + + storeClient: WakuStoreClient + deliveryCb: DeliveryFeedbackCallback + + wakuRelay: protocol.WakuRelay + wakuLightpushClient: WakuLightPushClient + +proc new*( + T: type SendMonitor, + storeClient: WakuStoreClient, + wakuRelay: protocol.WakuRelay, + wakuLightpushClient: WakuLightPushClient, +): Result[T, string] = + if wakuRelay.isNil() and wakuLightpushClient.isNil(): + return err( + "Could not create SendMonitor. wakuRelay or wakuLightpushClient should be set" + ) + + let notDeliveredStorage = ?NotDeliveredStorage.new() + + let sendMonitor = SendMonitor( + notDeliveredStorage: notDeliveredStorage, + storeClient: storeClient, + wakuRelay: wakuRelay, + wakuLightpushClient: wakuLightPushClient, + ) + + if not wakuRelay.isNil(): + wakuRelay.addPublishObserver(sendMonitor) + + if not wakuLightpushClient.isNil(): + wakuLightpushClient.addPublishObserver(sendMonitor) + + return ok(sendMonitor) + +proc performFeedbackAndCleanup( + self: SendMonitor, + msgsToDiscard: Table[WakuMessageHash, DeliveryInfo], + success: DeliverySuccess, + dir: DeliveryDirection, + comment: string, +) = + ## This procs allows to bring delivery feedback to the API client + ## It requires a 'deliveryCb' to be registered beforehand. + if self.deliveryCb.isNil(): + error "deliveryCb is nil in performFeedbackAndCleanup", + success, dir, comment, hashes = toSeq(msgsToDiscard.keys).mapIt(shortLog(it)) + return + + for hash, deliveryInfo in msgsToDiscard: + debug "send monitor performFeedbackAndCleanup", + success, dir, comment, msg_hash = shortLog(hash) + + self.deliveryCb(success, dir, comment, hash, deliveryInfo.msg) + self.publishedMessages.del(hash) + +proc checkMsgsInStore( + self: SendMonitor, msgsToValidate: Table[WakuMessageHash, DeliveryInfo] +): Future[ + Result[ + tuple[ + publishedCorrectly: Table[WakuMessageHash, DeliveryInfo], + notYetPublished: Table[WakuMessageHash, DeliveryInfo], + ], + void, + ] +] {.async.} = + let hashesToValidate = toSeq(msgsToValidate.keys) + + let storeResp: StoreQueryResponse = ( + await self.storeClient.queryToAny( + StoreQueryRequest(includeData: false, messageHashes: hashesToValidate) + ) + ).valueOr: + error "checkMsgsInStore failed to get remote msgHashes", + hashes = hashesToValidate.mapIt(shortLog(it)), error = $error + return err() + + let publishedHashes = storeResp.messages.mapIt(it.messageHash) + + var notYetPublished: Table[WakuMessageHash, DeliveryInfo] + var publishedCorrectly: Table[WakuMessageHash, DeliveryInfo] + + for msgHash, deliveryInfo in msgsToValidate.pairs: + if publishedHashes.contains(msgHash): + publishedCorrectly[msgHash] = deliveryInfo + self.publishedMessages.del(msgHash) ## we will no longer track that message + else: + notYetPublished[msgHash] = deliveryInfo + + return ok((publishedCorrectly: publishedCorrectly, notYetPublished: notYetPublished)) + +proc processMessages(self: SendMonitor) {.async.} = + var msgsToValidate: Table[WakuMessageHash, DeliveryInfo] + var msgsToDiscard: Table[WakuMessageHash, DeliveryInfo] + + let now = getNowInNanosecondTime() + let timeToCheckThreshold = now - ArchiveTime.nanos + let maxLifeTime = now - MaxTimeInCache.nanos + + for hash, deliveryInfo in self.publishedMessages.pairs: + if deliveryInfo.msg.timestamp < maxLifeTime: + ## message is too old + msgsToDiscard[hash] = deliveryInfo + + if deliveryInfo.msg.timestamp < timeToCheckThreshold: + msgsToValidate[hash] = deliveryInfo + + ## Discard the messages that are too old + self.performFeedbackAndCleanup( + msgsToDiscard, DeliverySuccess.UNSUCCESSFUL, DeliveryDirection.PUBLISHING, + "Could not publish messages. Please try again.", + ) + + let (publishedCorrectly, notYetPublished) = ( + await self.checkMsgsInStore(msgsToValidate) + ).valueOr: + return ## the error log is printed in checkMsgsInStore + + ## Give positive feedback for the correctly published messages + self.performFeedbackAndCleanup( + publishedCorrectly, DeliverySuccess.SUCCESSFUL, DeliveryDirection.PUBLISHING, + "messages published correctly", + ) + + ## Try to publish again + for msgHash, deliveryInfo in notYetPublished.pairs: + let pubsubTopic = deliveryInfo.pubsubTopic + let msg = deliveryInfo.msg + if not self.wakuRelay.isNil(): + debug "trying to publish again with wakuRelay", msgHash, pubsubTopic + let ret = await self.wakuRelay.publish(pubsubTopic, msg) + if ret == 0: + error "could not publish with wakuRelay.publish", msgHash, pubsubTopic + continue + + if not self.wakuLightpushClient.isNil(): + debug "trying to publish again with wakuLightpushClient", msgHash, pubsubTopic + (await self.wakuLightpushClient.publishToAny(pubsubTopic, msg)).isOkOr: + error "could not publish with publishToAny", error = $error + continue + +proc checkIfMessagesStored(self: SendMonitor) {.async.} = + ## Continuously monitors that the sent messages have been received by a store node + while true: + await self.processMessages() + await sleepAsync(SendCheckInterval) + +method onMessagePublished( + self: SendMonitor, pubsubTopic: string, msg: WakuMessage +) {.gcsafe, raises: [].} = + ## Implementation of the PublishObserver interface. + ## + ## When publishing a message either through relay or lightpush, we want to add some extra effort + ## to make sure it is received to one store node. Hence, keep track of those published messages. + + debug "onMessagePublished" + let msgHash = computeMessageHash(pubSubTopic, msg) + + if not self.publishedMessages.hasKey(msgHash): + self.publishedMessages[msgHash] = DeliveryInfo(pubsubTopic: pubsubTopic, msg: msg) + +proc startSendMonitor*(self: SendMonitor) = + self.msgStoredCheckerHandle = self.checkIfMessagesStored() + +proc stopSendMonitor*(self: SendMonitor) = + self.msgStoredCheckerHandle.cancel() + +proc setDeliveryCallback*(self: SendMonitor, deliveryCb: DeliveryFeedbackCallback) = + self.deliveryCb = deliveryCb diff --git a/waku/node/delivery_monitor/subscriptions_observer.nim b/waku/node/delivery_monitor/subscriptions_observer.nim new file mode 100644 index 0000000000..0c5d552210 --- /dev/null +++ b/waku/node/delivery_monitor/subscriptions_observer.nim @@ -0,0 +1,13 @@ +import chronicles + +type SubscriptionObserver* = ref object of RootObj + +method onSubscribe*( + self: SubscriptionObserver, pubsubTopic: string, contentTopics: seq[string] +) {.base, gcsafe, raises: [].} = + error "onSubscribe not implemented" + +method onUnsubscribe*( + self: SubscriptionObserver, pubsubTopic: string, contentTopics: seq[string] +) {.gcsafe, raises: [].} = + error "onUnsubscribe not implemented" diff --git a/waku/node/peer_manager/peer_manager.nim b/waku/node/peer_manager/peer_manager.nim index 9c5c31df1e..87506e7a4c 100644 --- a/waku/node/peer_manager/peer_manager.nim +++ b/waku/node/peer_manager/peer_manager.nim @@ -226,6 +226,10 @@ proc connectRelay*( return false +proc disconnectNode*(pm: PeerManager, peer: RemotePeerInfo) {.async.} = + let peerId = peer.peerId + await pm.switch.disconnect(peerId) + # Dialing should be used for just protocols that require a stream to write and read # This shall not be used to dial Relay protocols, since that would create # unneccesary unused streams. @@ -560,46 +564,6 @@ proc addServicePeer*(pm: PeerManager, remotePeerInfo: RemotePeerInfo, proto: str pm.addPeer(remotePeerInfo) -proc reconnectPeers*( - pm: PeerManager, proto: string, backoff: chronos.Duration = chronos.seconds(0) -) {.async.} = - ## Reconnect to peers registered for this protocol. This will update connectedness. - ## Especially useful to resume connections from persistent storage after a restart. - - trace "Reconnecting peers", proto = proto - - # Proto is not persisted, we need to iterate over all peers. - for peerInfo in pm.peerStore.peers(protocolMatcher(proto)): - # Check that the peer can be connected - if peerInfo.connectedness == CannotConnect: - error "Not reconnecting to unreachable or non-existing peer", - peerId = peerInfo.peerId - continue - - # Respect optional backoff period where applicable. - let - # TODO: Add method to peerStore (eg isBackoffExpired()) - disconnectTime = Moment.init(peerInfo.disconnectTime, Second) # Convert - currentTime = Moment.init(getTime().toUnix, Second) - # Current time comparable to persisted value - backoffTime = disconnectTime + backoff - currentTime - # Consider time elapsed since last disconnect - - trace "Respecting backoff", - backoff = backoff, - disconnectTime = disconnectTime, - currentTime = currentTime, - backoffTime = backoffTime - - # TODO: This blocks the whole function. Try to connect to another peer in the meantime. - if backoffTime > ZeroDuration: - trace "Backing off before reconnect...", - peerId = peerInfo.peerId, backoffTime = backoffTime - # We disconnected recently and still need to wait for a backoff period before connecting - await sleepAsync(backoffTime) - - discard await pm.connectRelay(peerInfo) - #################### # Dialer interface # #################### @@ -647,7 +611,7 @@ proc connectToNodes*( if nodes.len == 0: return - info "Dialing multiple peers", numOfPeers = nodes.len + info "Dialing multiple peers", numOfPeers = nodes.len, nodes = $nodes var futConns: seq[Future[bool]] var connectedPeers: seq[RemotePeerInfo] @@ -685,6 +649,30 @@ proc connectToNodes*( # later. await sleepAsync(chronos.seconds(5)) +proc reconnectPeers*( + pm: PeerManager, proto: string, backoffTime: chronos.Duration = chronos.seconds(0) +) {.async.} = + ## Reconnect to peers registered for this protocol. This will update connectedness. + ## Especially useful to resume connections from persistent storage after a restart. + + debug "Reconnecting peers", proto = proto + + # Proto is not persisted, we need to iterate over all peers. + for peerInfo in pm.peerStore.peers(protocolMatcher(proto)): + # Check that the peer can be connected + if peerInfo.connectedness == CannotConnect: + error "Not reconnecting to unreachable or non-existing peer", + peerId = peerInfo.peerId + continue + + if backoffTime > ZeroDuration: + debug "Backing off before reconnect", + peerId = peerInfo.peerId, backoffTime = backoffTime + # We disconnected recently and still need to wait for a backoff period before connecting + await sleepAsync(backoffTime) + + await pm.connectToNodes(@[peerInfo]) + proc connectedPeers*(pm: PeerManager, protocol: string): (seq[PeerId], seq[PeerId]) = ## Returns the peerIds of physical connections (in and out) ## containing at least one stream with the given protocol. @@ -734,9 +722,8 @@ proc connectToRelayPeers*(pm: PeerManager) {.async.} = let totalRelayPeers = inRelayPeers.len + outRelayPeers.len let inPeersTarget = maxConnections - pm.outRelayPeersTarget - # TODO: Temporally disabled. Might be causing connection issues - #if inRelayPeers.len > pm.inRelayPeersTarget: - # await pm.pruneInRelayConns(inRelayPeers.len - pm.inRelayPeersTarget) + if inRelayPeers.len > pm.inRelayPeersTarget: + await pm.pruneInRelayConns(inRelayPeers.len - pm.inRelayPeersTarget) if outRelayPeers.len >= pm.outRelayPeersTarget: return @@ -966,7 +953,21 @@ proc relayConnectivityLoop*(pm: PeerManager) {.async.} = await pm.manageRelayPeers() else: await pm.connectToRelayPeers() - await sleepAsync(ConnectivityLoopInterval) + let + (inRelayPeers, outRelayPeers) = pm.connectedPeers(WakuRelayCodec) + excessInConns = max(inRelayPeers.len - pm.inRelayPeersTarget, 0) + + # One minus the percentage of excess connections relative to the target, limited to 100% + # We calculate one minus this percentage because we want the factor to be inversely proportional to the number of excess peers + inFactor = 1 - min(excessInConns / pm.inRelayPeersTarget, 1) + # Percentage of out relay peers relative to the target + outFactor = min(outRelayPeers.len / pm.outRelayPeersTarget, 1) + factor = min(outFactor, inFactor) + dynamicSleepInterval = + chronos.seconds(int(float(ConnectivityLoopInterval.seconds()) * factor)) + + # Shorten the connectivity loop interval dynamically based on percentage of peers to fill or connections to prune + await sleepAsync(dynamicSleepInterval) proc logAndMetrics(pm: PeerManager) {.async.} = heartbeat "Scheduling log and metrics run", LogAndMetricsInterval: diff --git a/waku/node/peer_manager/waku_peer_store.nim b/waku/node/peer_manager/waku_peer_store.nim index a7db829f29..09d6ebc658 100644 --- a/waku/node/peer_manager/waku_peer_store.nim +++ b/waku/node/peer_manager/waku_peer_store.nim @@ -55,8 +55,7 @@ proc get*(peerStore: PeerStore, peerId: PeerID): RemotePeerInfo = if peerStore[ENRBook][peerId] != default(enr.Record): some(peerStore[ENRBook][peerId]) else: - none(enr.Record) - , + none(enr.Record), protocols: peerStore[ProtoBook][peerId], agent: peerStore[AgentBook][peerId], protoVersion: peerStore[ProtoVersionBook][peerId], diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index b651cc35e6..b57f49681c 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -39,6 +39,7 @@ import ../waku_filter_v2/subscriptions as filter_subscriptions, ../waku_metadata, ../waku_rendezvous/protocol, + ../waku_sync, ../waku_lightpush/client as lightpush_client, ../waku_lightpush/common, ../waku_lightpush/protocol, @@ -104,6 +105,7 @@ type wakuPeerExchange*: WakuPeerExchange wakuMetadata*: WakuMetadata wakuSharding*: Sharding + wakuSync*: WakuSync enr*: enr.Record libp2pPing*: Ping rng*: ref rand.HmacDrbgContext @@ -112,6 +114,7 @@ type started*: bool # Indicates that node has started listening topicSubscriptionQueue*: AsyncEventQueue[SubscriptionEvent] contentTopicHandlers: Table[ContentTopic, TopicHandler] + rateLimitSettings*: ProtocolRateLimitSettings proc getAutonatService*(rng: ref HmacDrbgContext): AutonatService = ## AutonatService request other peers to dial us back @@ -162,6 +165,7 @@ proc new*( enr: enr, announcedAddresses: netConfig.announcedAddresses, topicSubscriptionQueue: queue, + rateLimitSettings: DefaultProtocolRateLimit, ) return node @@ -194,6 +198,45 @@ proc connectToNodes*( # NOTE Connects to the node without a give protocol, which automatically creates streams for relay await peer_manager.connectToNodes(node.peerManager, nodes, source = source) +proc disconnectNode*(node: WakuNode, remotePeer: RemotePeerInfo) {.async.} = + await peer_manager.disconnectNode(node.peerManager, remotePeer) + +## Waku Sync + +proc mountWakuSync*( + node: WakuNode, + maxFrameSize: int = DefaultMaxFrameSize, + syncRange: timer.Duration = DefaultSyncRange, + syncInterval: timer.Duration = DefaultSyncInterval, + relayJitter: Duration = DefaultGossipSubJitter, +): Future[Result[void, string]] {.async.} = + if not node.wakuSync.isNil(): + return err("already mounted") + + node.wakuSync = ( + await WakuSync.new( + peerManager = node.peerManager, + maxFrameSize = maxFrameSize, + syncRange = syncRange, + syncInterval = syncInterval, + relayJitter = relayJitter, + wakuArchive = node.wakuArchive, + wakuStoreClient = node.wakuStoreClient, + ) + ).valueOr: + return err("initialization failed: " & error) + + let catchable = catch: + node.switch.mount(node.wakuSync, protocolMatcher(WakuSyncCodec)) + + if catchable.isErr(): + return err("switch mounting failed: " & catchable.error.msg) + + if node.started: + node.wakuSync.start() + + return ok() + ## Waku Metadata proc mountMetadata*(node: WakuNode, clusterId: uint32): Result[void, string] = @@ -228,14 +271,6 @@ proc registerRelayDefaultHandler(node: WakuNode, topic: PubsubTopic) = proc traceHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} = let msg_hash = topic.computeMessageHash(msg).to0xHex() - - notice "waku.relay received", - my_peer_id = node.peerId, - pubsubTopic = topic, - msg_hash = msg_hash, - receivedTime = getNowInNanosecondTime(), - payloadSizeBytes = msg.payload.len - let msgSizeKB = msg.payload.len / 1000 waku_node_messages.inc(labelValues = ["relay"]) @@ -258,12 +293,19 @@ proc registerRelayDefaultHandler(node: WakuNode, topic: PubsubTopic) = await node.wakuArchive.handleMessage(topic, msg) + proc syncHandler(topic: PubsubTopic, msg: WakuMessage) {.async.} = + if node.wakuSync.isNil(): + return + + node.wakuSync.messageIngress(topic, msg) + let defaultHandler = proc( topic: PubsubTopic, msg: WakuMessage ): Future[void] {.async, gcsafe.} = await traceHandler(topic, msg) await filterHandler(topic, msg) await archiveHandler(topic, msg) + await syncHandler(topic, msg) discard node.wakuRelay.subscribe(topic, defaultHandler) @@ -284,7 +326,7 @@ proc subscribe*( error "Autosharding error", error = error return - (shard, some(subscription.topic)) + ($shard, some(subscription.topic)) of PubsubSub: (subscription.topic, none(ContentTopic)) else: @@ -319,7 +361,7 @@ proc unsubscribe*(node: WakuNode, subscription: SubscriptionEvent) = error "Autosharding error", error = error return - (shard, some(subscription.topic)) + ($shard, some(subscription.topic)) of PubsubUnsub: (subscription.topic, none(ContentTopic)) else: @@ -352,14 +394,14 @@ proc publish*( if node.wakuRelay.isNil(): let msg = "Invalid API call to `publish`. WakuRelay not mounted. Try `lightpush` instead." - error "publish error", msg = msg + error "publish error", err = msg # TODO: Improve error handling return err(msg) let pubsubTopic = pubsubTopicOp.valueOr: node.wakuSharding.getShard(message.contentTopic).valueOr: let msg = "Autosharding error: " & error - error "publish error", msg = msg + error "publish error", err = msg return err(msg) #TODO instead of discard return error when 0 peers received the message @@ -400,7 +442,7 @@ proc startRelay*(node: WakuNode) {.async.} = proc mountRelay*( node: WakuNode, - pubsubTopics: seq[string] = @[], + shards: seq[RelayShard] = @[], peerExchangeHandler = none(RoutingRecordsHandler), maxMessageSize = int(DefaultMaxWakuMessageSize), ) {.async, gcsafe.} = @@ -429,11 +471,11 @@ proc mountRelay*( node.switch.mount(node.wakuRelay, protocolMatcher(WakuRelayCodec)) - info "relay mounted successfully", pubsubTopics = pubsubTopics + info "relay mounted successfully", shards = shards - # Subscribe to topics - for pubsubTopic in pubsubTopics: - node.subscribe((kind: PubsubSub, topic: pubsubTopic)) + # Subscribe to shards + for shard in shards: + node.subscribe((kind: PubsubSub, topic: $shard)) ## Waku filter @@ -444,7 +486,7 @@ proc mountFilter*( maxFilterPeers: uint32 = filter_subscriptions.MaxFilterPeers, maxFilterCriteriaPerPeer: uint32 = filter_subscriptions.MaxFilterCriteriaPerPeer, messageCacheTTL: Duration = filter_subscriptions.MessageCacheTTL, - rateLimitSetting: RateLimitSetting = FilterPerPeerRateLimit, + rateLimitSetting: RateLimitSetting = FilterDefaultPerPeerRateLimit, ) {.async: (raises: []).} = ## Mounting filter v2 protocol @@ -732,6 +774,7 @@ proc toArchiveQuery( endTime: request.endTime, pageSize: request.pageSize.uint, direction: request.direction, + requestId: request.requestId, ) # TODO: Review this mapping logic. Maybe, move it to the appplication code @@ -873,6 +916,7 @@ proc toArchiveQuery(request: StoreQueryRequest): waku_archive.ArchiveQuery = query.hashes = request.messageHashes query.cursor = request.paginationCursor query.direction = request.paginationForward + query.requestId = request.requestId if request.paginationLimit.isSome(): query.pageSize = uint(request.paginationLimit.get()) @@ -1060,7 +1104,7 @@ proc lightpushPublish*( peerOpt = node.peerManager.selectPeer(WakuLightPushCodec) if peerOpt.isNone(): let msg = "no suitable remote peers" - error "failed to publish message", msg = msg + error "failed to publish message", err = msg return err(msg) elif not node.wakuLightPush.isNil(): peerOpt = some(RemotePeerInfo.init($node.switch.peerInfo.peerId)) @@ -1105,11 +1149,14 @@ proc mountRlnRelay*( ## Waku peer-exchange proc mountPeerExchange*( - node: WakuNode, cluster: Option[uint16] = none(uint16) + node: WakuNode, + cluster: Option[uint16] = none(uint16), + rateLimit: RateLimitSetting = DefaultGlobalNonRelayRateLimit, ) {.async: (raises: []).} = info "mounting waku peer exchange" - node.wakuPeerExchange = WakuPeerExchange.new(node.peerManager, cluster) + node.wakuPeerExchange = + WakuPeerExchange.new(node.peerManager, cluster, some(rateLimit)) if node.started: try: @@ -1124,10 +1171,15 @@ proc mountPeerExchange*( proc fetchPeerExchangePeers*( node: Wakunode, amount: uint64 -): Future[Result[int, string]] {.async: (raises: []).} = +): Future[Result[int, PeerExchangeResponseStatus]] {.async: (raises: []).} = if node.wakuPeerExchange.isNil(): error "could not get peers from px, waku peer-exchange is nil" - return err("PeerExchange is not mounted") + return err( + ( + status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE, + status_desc: some("PeerExchange is not mounted"), + ) + ) info "Retrieving peer info via peer exchange protocol" let pxPeersRes = await node.wakuPeerExchange.request(amount) @@ -1145,7 +1197,7 @@ proc fetchPeerExchangePeers*( else: warn "failed to retrieve peer info via peer exchange protocol", error = pxPeersRes.error - return err("Peer exchange failure: " & $pxPeersRes.error) + return err(pxPeersRes.error) # TODO: Move to application module (e.g., wakunode2.nim) proc setPeerExchangePeer*( @@ -1294,6 +1346,8 @@ proc start*(node: WakuNode) {.async.} = await node.wakuRendezvous.start() except CatchableError: error "failed to start rendezvous", error = getCurrentExceptionMsg() + if not node.wakuSync.isNil(): + node.wakuSync.start() ## The switch uses this mapper to update peer info addrs ## with announced addrs after start @@ -1343,3 +1397,10 @@ proc isReady*(node: WakuNode): Future[bool] {.async: (raises: [Exception]).} = return true return await node.wakuRlnRelay.isReady() ## TODO: add other protocol `isReady` checks + +proc setRateLimits*(node: WakuNode, limits: seq[string]): Result[void, string] = + let rateLimitConfig = ProtocolRateLimitSettings.parse(limits) + if rateLimitConfig.isErr(): + return err("invalid rate limit settings:" & rateLimitConfig.error) + node.rateLimitSettings = rateLimitConfig.get() + return ok() diff --git a/waku/node/waku_switch.nim b/waku/node/waku_switch.nim index 65e7c19992..db1dc51bb2 100644 --- a/waku/node/waku_switch.nim +++ b/waku/node/waku_switch.nim @@ -30,7 +30,7 @@ proc getSecureKey(path: string): TLSPrivateKey {.raises: [Defect, IOError].} = let key = TLSPrivateKey.init(stringkey) return key except TLSStreamProtocolError as exc: - debug "exception raised from getSecureKey", msg = exc.msg + debug "exception raised from getSecureKey", err = exc.msg proc getSecureCert(path: string): TLSCertificate {.raises: [Defect, IOError].} = trace "Certificate path is.", path = path @@ -39,7 +39,7 @@ proc getSecureCert(path: string): TLSCertificate {.raises: [Defect, IOError].} = let cert = TLSCertificate.init(stringCert) return cert except TLSStreamProtocolError as exc: - debug "exception raised from getSecureCert", msg = exc.msg + debug "exception raised from getSecureCert", err = exc.msg proc withWssTransport*( b: SwitchBuilder, secureKeyPath: string, secureCertPath: string diff --git a/waku/waku_api/rest/admin/handlers.nim b/waku/waku_api/rest/admin/handlers.nim index 435d19de25..9570e413a4 100644 --- a/waku/waku_api/rest/admin/handlers.nim +++ b/waku/waku_api/rest/admin/handlers.nim @@ -16,6 +16,7 @@ import ../../../waku_relay, ../../../waku_peer_exchange, ../../../waku_node, + ../../../waku_sync, ../../../node/peer_manager, ../responses, ../serdes, @@ -101,6 +102,18 @@ proc installAdminV1GetPeersHandler(router: var RestRouter, node: WakuNode) = ) tuplesToWakuPeers(peers, pxPeers) + if not node.wakuSync.isNil(): + # Map WakuSync peers to WakuPeers and add to return list + let syncPeers = node.peerManager.peerStore.peers(WakuSyncCodec).mapIt( + ( + multiaddr: constructMultiaddrStr(it), + protocol: WakuSyncCodec, + connected: it.connectedness == Connectedness.Connected, + origin: it.origin, + ) + ) + tuplesToWakuPeers(peers, syncPeers) + let resp = RestApiResponse.jsonResponse(peers, status = Http200) if resp.isErr(): error "An error ocurred while building the json respose: ", error = resp.error @@ -115,12 +128,13 @@ proc installAdminV1PostPeersHandler(router: var RestRouter, node: WakuNode) = contentBody: Option[ContentBody] ) -> RestApiResponse: let peers: seq[string] = decodeRequestBody[seq[string]](contentBody).valueOr: - return RestApiResponse.badRequest(fmt("Failed to decode request: {error}")) + let e = $error + return RestApiResponse.badRequest(fmt("Failed to decode request: {e}")) for i, peer in peers: let peerInfo = parsePeerInfo(peer).valueOr: - return - RestApiResponse.badRequest(fmt("Couldn't parse remote peer info: {error}")) + let e = $error + return RestApiResponse.badRequest(fmt("Couldn't parse remote peer info: {e}")) if not (await node.peerManager.connectRelay(peerInfo, source = "rest")): return RestApiResponse.badRequest( diff --git a/waku/waku_api/rest/builder.nim b/waku/waku_api/rest/builder.nim index ebf1c7f963..325dcce06f 100644 --- a/waku/waku_api/rest/builder.nim +++ b/waku/waku_api/rest/builder.nim @@ -33,7 +33,7 @@ proc startRestServerEsentials*( nodeHealthMonitor: WakuNodeHealthMonitor, conf: WakuNodeConf ): Result[WakuRestServerRef, string] = if not conf.rest: - return + return ok(nil) let requestErrorHandler: RestRequestErrorHandler = proc( error: RestRequestError, request: HttpRequestRef @@ -113,7 +113,7 @@ proc startRestServerProtocolSupport*( conf: WakuNodeConf, ): Result[void, string] = if not conf.rest: - return + return ok() var router = restServer.router ## Admin REST API @@ -132,7 +132,8 @@ proc startRestServerProtocolSupport*( let handler = messageCacheHandler(cache) - for pubsubTopic in conf.pubsubTopics: + for shard in conf.shards: + let pubsubTopic = $RelayShard(clusterId: conf.clusterId, shardId: shard) cache.pubsubSubscribe(pubsubTopic) node.subscribe((kind: PubsubSub, topic: pubsubTopic), some(handler)) diff --git a/waku/waku_api/rest/filter/types.nim b/waku/waku_api/rest/filter/types.nim index 7497d6a78e..0506a7a741 100644 --- a/waku/waku_api/rest/filter/types.nim +++ b/waku/waku_api/rest/filter/types.nim @@ -58,8 +58,7 @@ proc toFilterWakuMessage*(msg: WakuMessage): FilterWakuMessage = if msg.meta.len > 0: some(base64.encode(msg.meta)) else: - none(Base64String) - , + none(Base64String), ephemeral: some(msg.ephemeral), ) @@ -239,8 +238,7 @@ proc readValue*( if pubsubTopic.isNone() or pubsubTopic.get() == "": none(string) else: - some(pubsubTopic.get()) - , + some(pubsubTopic.get()), contentFilters: contentFilters.get(), ) @@ -315,8 +313,7 @@ proc readValue*( if pubsubTopic.isNone() or pubsubTopic.get() == "": none(string) else: - some(pubsubTopic.get()) - , + some(pubsubTopic.get()), contentFilters: contentFilters.get(), ) @@ -364,8 +361,7 @@ proc readValue*( if pubsubTopic.isNone() or pubsubTopic.get() == "": none(string) else: - some(pubsubTopic.get()) - , + some(pubsubTopic.get()), contentFilters: contentFilters.get(), ) diff --git a/waku/waku_api/rest/legacy_store/handlers.nim b/waku/waku_api/rest/legacy_store/handlers.nim index f0747a8de6..d960f24eac 100644 --- a/waku/waku_api/rest/legacy_store/handlers.nim +++ b/waku/waku_api/rest/legacy_store/handlers.nim @@ -183,8 +183,9 @@ proc retrieveMsgsFromSelfNode( let storeResp = selfResp.toStoreResponseRest() let resp = RestApiResponse.jsonResponse(storeResp, status = Http200).valueOr: const msg = "Error building the json respose" - error msg, error = error - return RestApiResponse.internalServerError(fmt("{msg} [{error}]")) + let e = $error + error msg, error = e + return RestApiResponse.internalServerError(fmt("{msg} [{e}]")) return resp diff --git a/waku/waku_api/rest/legacy_store/types.nim b/waku/waku_api/rest/legacy_store/types.nim index 0ae36e05e5..eee3ac2d82 100644 --- a/waku/waku_api/rest/legacy_store/types.nim +++ b/waku/waku_api/rest/legacy_store/types.nim @@ -110,8 +110,7 @@ proc toStoreResponseRest*(histResp: HistoryResponse): StoreResponseRest = if message.meta.len > 0: some(base64.encode(message.meta)) else: - none(Base64String) - , + none(Base64String), ) var storeWakuMsgs: seq[StoreWakuMessage] diff --git a/waku/waku_api/rest/lightpush/types.nim b/waku/waku_api/rest/lightpush/types.nim index 3d8d0715fe..f499600b7a 100644 --- a/waku/waku_api/rest/lightpush/types.nim +++ b/waku/waku_api/rest/lightpush/types.nim @@ -62,7 +62,6 @@ proc readValue*( if pubsubTopic.isNone() or pubsubTopic.get() == "": none(string) else: - some(pubsubTopic.get()) - , + some(pubsubTopic.get()), message: message.get(), ) diff --git a/waku/waku_api/rest/relay/handlers.nim b/waku/waku_api/rest/relay/handlers.nim index 589100ead0..7ee0ee7e33 100644 --- a/waku/waku_api/rest/relay/handlers.nim +++ b/waku/waku_api/rest/relay/handlers.nim @@ -161,6 +161,9 @@ proc installRelayApiHandlers*( (await node.wakuRelay.validateMessage(pubsubTopic, message)).isOkOr: return RestApiResponse.badRequest("Failed to publish: " & error) + # Log for message tracking purposes + 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 debug "Publishing message", pubSubTopic = pubSubTopic, rln = not node.wakuRlnRelay.isNil() @@ -259,7 +262,7 @@ proc installRelayApiHandlers*( let pubsubTopic = node.wakuSharding.getShard(message.contentTopic).valueOr: let msg = "Autosharding error: " & error - error "publish error", msg = msg + error "publish error", err = msg return RestApiResponse.badRequest("Failed to publish. " & msg) # if RLN is mounted, append the proof to the message @@ -272,11 +275,14 @@ proc installRelayApiHandlers*( (await node.wakuRelay.validateMessage(pubsubTopic, message)).isOkOr: return RestApiResponse.badRequest("Failed to publish: " & error) + # Log for message tracking purposes + 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 debug "Publishing message", contentTopic = message.contentTopic, rln = not node.wakuRlnRelay.isNil() - var publishFut = node.publish(some(pubsubTopic), message) + var publishFut = node.publish(some($pubsubTopic), message) if not await publishFut.withTimeout(futTimeout): return RestApiResponse.internalServerError("Failed to publish: timedout") diff --git a/waku/waku_api/rest/relay/types.nim b/waku/waku_api/rest/relay/types.nim index 98957c747e..1ffb59384e 100644 --- a/waku/waku_api/rest/relay/types.nim +++ b/waku/waku_api/rest/relay/types.nim @@ -34,8 +34,7 @@ proc toRelayWakuMessage*(msg: WakuMessage): RelayWakuMessage = if msg.meta.len > 0: some(base64.encode(msg.meta)) else: - none(Base64String) - , + none(Base64String), ephemeral: some(msg.ephemeral), ) diff --git a/waku/waku_api/rest/store/handlers.nim b/waku/waku_api/rest/store/handlers.nim index b835645b0d..663d796eab 100644 --- a/waku/waku_api/rest/store/handlers.nim +++ b/waku/waku_api/rest/store/handlers.nim @@ -50,8 +50,9 @@ proc performStoreQuery( let resp = RestApiResponse.jsonResponse(res, status = Http200).valueOr: const msg = "Error building the json respose" - error msg, error = error - return RestApiResponse.internalServerError(fmt("{msg} [{error}]")) + let e = $error + error msg, error = e + return RestApiResponse.internalServerError(fmt("{msg} [{e}]")) return resp @@ -166,8 +167,9 @@ proc retrieveMsgsFromSelfNode( let resp = RestApiResponse.jsonResponse(storeResp, status = Http200).valueOr: const msg = "Error building the json respose" - error msg, error = error - return RestApiResponse.internalServerError(fmt("{msg} [{error}]")) + let e = $error + error msg, error = e + return RestApiResponse.internalServerError(fmt("{msg} [{e}]")) return resp diff --git a/waku/waku_archive/archive.nim b/waku/waku_archive/archive.nim index caf746170a..7e1cce9dd4 100644 --- a/waku/waku_archive/archive.nim +++ b/waku/waku_archive/archive.nim @@ -24,6 +24,7 @@ const # Retention policy WakuArchiveDefaultRetentionPolicyInterval* = chronos.minutes(30) + WakuArchiveDefaultRetentionPolicyIntervalWhenError* = chronos.minutes(1) # Metrics reporting WakuArchiveDefaultMetricsReportInterval* = chronos.minutes(30) @@ -97,6 +98,7 @@ proc handleMessage*( contentTopic = msg.contentTopic, timestamp = msg.timestamp, error = error + return trace "message archived", hash_hash = msgHash.to0xHex(), @@ -143,6 +145,7 @@ proc findMessages*( hashes = query.hashes, maxPageSize = maxPageSize + 1, ascendingOrder = isAscendingOrder, + requestId = query.requestId, ) ).valueOr: return err(ArchiveError(kind: ArchiveErrorKind.DRIVER_ERROR, cause: error)) @@ -192,6 +195,9 @@ proc periodicRetentionPolicy(self: WakuArchive) {.async.} = (await policy.execute(self.driver)).isOkOr: waku_archive_errors.inc(labelValues = [retPolicyFailure]) error "failed execution of retention policy", error = error + await sleepAsync(WakuArchiveDefaultRetentionPolicyIntervalWhenError) + ## in case of error, let's try again faster + continue await sleepAsync(WakuArchiveDefaultRetentionPolicyInterval) diff --git a/waku/waku_archive/common.nim b/waku/waku_archive/common.nim index b88b70f050..731dc11c45 100644 --- a/waku/waku_archive/common.nim +++ b/waku/waku_archive/common.nim @@ -18,6 +18,7 @@ type hashes*: seq[WakuMessageHash] pageSize*: uint direction*: PagingDirection + requestId*: string ArchiveResponse* = object hashes*: seq[WakuMessageHash] diff --git a/waku/waku_archive/driver.nim b/waku/waku_archive/driver.nim index 49174b5719..4d5cedd66d 100644 --- a/waku/waku_archive/driver.nim +++ b/waku/waku_archive/driver.nim @@ -37,6 +37,7 @@ method getMessages*( hashes = newSeq[WakuMessageHash](0), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.base, async.} = discard diff --git a/waku/waku_archive/driver/builder.nim b/waku/waku_archive/driver/builder.nim index 1825477b5f..ddc447685f 100644 --- a/waku/waku_archive/driver/builder.nim +++ b/waku/waku_archive/driver/builder.nim @@ -102,6 +102,8 @@ proc new*( ## Hence, this should be run after the migration is completed. asyncSpawn driver.startPartitionFactory(onFatalErrorAction) + driver.startAnalyzeTableLoop() + info "waiting for a partition to be created" for i in 0 ..< 100: if driver.containsAnyPartition(): diff --git a/waku/waku_archive/driver/postgres_driver/migrations.nim b/waku/waku_archive/driver/postgres_driver/migrations.nim index f99146bbfd..6409a07069 100644 --- a/waku/waku_archive/driver/postgres_driver/migrations.nim +++ b/waku/waku_archive/driver/postgres_driver/migrations.nim @@ -9,7 +9,7 @@ import logScope: topics = "waku archive migration" -const SchemaVersion* = 6 # increase this when there is an update in the database schema +const SchemaVersion* = 7 # increase this when there is an update in the database schema proc breakIntoStatements*(script: string): seq[string] = ## Given a full migration script, that can potentially contain a list diff --git a/waku/waku_archive/driver/postgres_driver/partitions_manager.nim b/waku/waku_archive/driver/postgres_driver/partitions_manager.nim index 0591209ce8..fe8209d51b 100644 --- a/waku/waku_archive/driver/postgres_driver/partitions_manager.nim +++ b/waku/waku_archive/driver/postgres_driver/partitions_manager.nim @@ -15,7 +15,7 @@ logScope: type TimeRange* = tuple[beginning: int64, `end`: int64] type - Partition = object + Partition* = object name: string timeRange: TimeRange @@ -132,5 +132,8 @@ proc calcEndPartitionTime*(startTime: Timestamp): Timestamp = proc getName*(partition: Partition): string = return partition.name +proc getTimeRange*(partition: Partition): TimeRange = + return partition.timeRange + func `==`*(a, b: Partition): bool {.inline.} = return a.name == b.name diff --git a/waku/waku_archive/driver/postgres_driver/postgres_driver.nim b/waku/waku_archive/driver/postgres_driver/postgres_driver.nim index 351d0f925b..a69fdb9f46 100644 --- a/waku/waku_archive/driver/postgres_driver/postgres_driver.nim +++ b/waku/waku_archive/driver/postgres_driver/postgres_driver.nim @@ -1,7 +1,7 @@ {.push raises: [].} import - std/[nre, options, sequtils, strutils, strformat, times], + std/[nre, options, sequtils, strutils, strformat, times, sugar], stew/[byteutils, arrayops], results, chronos, @@ -25,11 +25,17 @@ type PostgresDriver* = ref object of ArchiveDriver partitionMngr: PartitionManager futLoopPartitionFactory: Future[void] + futLoopAnalyzeTable: Future[void] + const InsertRowStmtName = "InsertRow" const InsertRowStmtDefinition = """INSERT INTO messages (id, messageHash, pubsubTopic, contentTopic, payload, version, timestamp, meta) VALUES ($1, $2, $3, $4, $5, $6, $7, CASE WHEN $8 = '' THEN NULL ELSE $8 END) ON CONFLICT DO NOTHING;""" +const InsertRowInMessagesLookupStmtName = "InsertRowMessagesLookup" +const InsertRowInMessagesLookupStmtDefinition = + """INSERT INTO messages_lookup (messageHash, timestamp) VALUES ($1, $2) ON CONFLICT DO NOTHING;""" + const SelectClause = """SELECT messageHash, pubsubTopic, contentTopic, payload, version, timestamp, meta FROM messages """ @@ -122,7 +128,9 @@ const SelectCursorByHashDef = """SELECT timestamp FROM messages WHERE messageHash = $1""" -const DefaultMaxNumConns = 50 +const + DefaultMaxNumConns = 50 + MaxHashesPerQuery = 100 proc new*( T: type PostgresDriver, @@ -301,28 +309,43 @@ method put*( ## until we completely remove the store/archive-v2 logic let fakeId = "0" + ( + ## Add the row to the messages table + await s.writeConnPool.runStmt( + InsertRowStmtName, + InsertRowStmtDefinition, + @[ + fakeId, messageHash, pubsubTopic, contentTopic, payload, version, timestamp, + meta, + ], + @[ + int32(fakeId.len), + int32(messageHash.len), + int32(pubsubTopic.len), + int32(contentTopic.len), + int32(payload.len), + int32(version.len), + int32(timestamp.len), + int32(meta.len), + ], + @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], + ) + ).isOkOr: + return err("could not put msg in messages table: " & $error) + + ## Now add the row to messages_lookup return await s.writeConnPool.runStmt( - InsertRowStmtName, - InsertRowStmtDefinition, - @[fakeId, messageHash, pubsubTopic, contentTopic, payload, version, timestamp, meta], - @[ - int32(fakeId.len), - int32(messageHash.len), - int32(pubsubTopic.len), - int32(contentTopic.len), - int32(payload.len), - int32(version.len), - int32(timestamp.len), - int32(meta.len), - ], - @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], + InsertRowInMessagesLookupStmtName, + InsertRowInMessagesLookupStmtDefinition, + @[messageHash, timestamp], + @[int32(messageHash.len), int32(timestamp.len)], + @[int32(0), int32(0)], ) method getAllMessages*( s: PostgresDriver ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## Retrieve all messages from the store. - var rows: seq[(WakuMessageHash, PubsubTopic, WakuMessage)] proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) @@ -345,8 +368,6 @@ proc getPartitionsList( ): Future[ArchiveDriverResult[seq[string]]] {.async.} = ## Retrieves the seq of partition table names. ## e.g: @["messages_1708534333_1708534393", "messages_1708534273_1708534333"] - - debug "beginning getPartitionsList" var partitions: seq[string] proc rowCallback(pqResult: ptr PGresult) = for iRow in 0 ..< pqResult.pqNtuples(): @@ -402,10 +423,10 @@ proc getMessagesArbitraryQuery( hexHashes: seq[string] = @[], maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## This proc allows to handle atypical queries. We don't use prepared statements for those. - debug "beginning getMessagesArbitraryQuery" var query = SelectClause var statements: seq[string] var args: seq[string] @@ -466,7 +487,7 @@ proc getMessagesArbitraryQuery( proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) - (await s.readConnPool.pgQuery(query, args, rowCallback)).isOkOr: + (await s.readConnPool.pgQuery(query, args, rowCallback, requestId)).isOkOr: return err("failed to run query: " & $error) return ok(rows) @@ -481,12 +502,11 @@ proc getMessageHashesArbitraryQuery( hexHashes: seq[string] = @[], maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[(WakuMessageHash, PubsubTopic, WakuMessage)]]] {. async .} = ## This proc allows to handle atypical queries. We don't use prepared statements for those. - - debug "beginning of getMessagesV2ArbitraryQuery" var query = """SELECT messageHash FROM messages""" var statements: seq[string] @@ -548,7 +568,7 @@ proc getMessageHashesArbitraryQuery( proc rowCallback(pqResult: ptr PGresult) = hashCallbackImpl(pqResult, rows) - (await s.readConnPool.pgQuery(query, args, rowCallback)).isOkOr: + (await s.readConnPool.pgQuery(query, args, rowCallback, requestId)).isOkOr: return err("failed to run query: " & $error) return ok(rows) @@ -563,13 +583,13 @@ proc getMessagesPreparedStmt( hashes: string, maxPageSize = DefaultPageSize, ascOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## This proc aims to run the most typical queries in a more performant way, ## i.e. by means of prepared statements. var rows: seq[(WakuMessageHash, PubsubTopic, WakuMessage)] - debug "beginning of getMessagesPreparedStmt" proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) @@ -596,6 +616,7 @@ proc getMessagesPreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err(stmtName & ": " & $error) @@ -636,6 +657,7 @@ proc getMessagesPreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err(stmtName & ": " & $error) @@ -652,13 +674,13 @@ proc getMessageHashesPreparedStmt( hashes: string, maxPageSize = DefaultPageSize, ascOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## This proc aims to run the most typical queries in a more performant way, ## i.e. by means of prepared statements. var rows: seq[(WakuMessageHash, PubsubTopic, WakuMessage)] - debug "beginning of getMessagesV2PreparedStmt" proc rowCallback(pqResult: ptr PGresult) = hashCallbackImpl(pqResult, rows) @@ -687,6 +709,7 @@ proc getMessageHashesPreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err(stmtName & ": " & $error) @@ -730,33 +753,88 @@ proc getMessageHashesPreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err(stmtName & ": " & $error) return ok(rows) -method getMessages*( - s: PostgresDriver, - includeData = true, - contentTopics = newSeq[ContentTopic](0), - pubsubTopic = none(PubsubTopic), - cursor = none(ArchiveCursor), - startTime = none(Timestamp), - endTime = none(Timestamp), - hashes = newSeq[WakuMessageHash](0), - maxPageSize = DefaultPageSize, - ascendingOrder = true, +proc getMessagesByMessageHashes( + s: PostgresDriver, hashes: string, maxPageSize: uint, requestId: string ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = - debug "beginning of getMessages" + ## Retrieves information only filtering by a given messageHashes list. + ## This proc levarages on the messages_lookup table to have better query performance + ## and only query the desired partitions in the partitioned messages table + var query = + fmt""" + WITH min_timestamp AS ( + SELECT MIN(timestamp) AS min_ts + FROM messages_lookup + WHERE messagehash IN ( + {hashes} + ) + ) + SELECT m.messageHash, pubsubTopic, contentTopic, payload, version, m.timestamp, meta + FROM messages m + INNER JOIN + messages_lookup l + ON + m.timestamp = l.timestamp + AND m.messagehash = l.messagehash + WHERE + l.timestamp >= (SELECT min_ts FROM min_timestamp) + AND l.messagehash IN ( + {hashes} + ) + ORDER BY + m.timestamp DESC, + m.messagehash DESC + LIMIT {maxPageSize}; + """ + + var rows: seq[(WakuMessageHash, PubsubTopic, WakuMessage)] + proc rowCallback(pqResult: ptr PGresult) = + rowCallbackImpl(pqResult, rows) + + ( + await s.readConnPool.pgQuery( + query = query, rowCallback = rowCallback, requestId = requestId + ) + ).isOkOr: + return err("failed to run query: " & $error) + + return ok(rows) + +proc getMessagesWithinLimits( + self: PostgresDriver, + includeData: bool, + contentTopics: seq[ContentTopic], + pubsubTopic: Option[PubsubTopic], + cursor: Option[ArchiveCursor], + startTime: Option[Timestamp], + endTime: Option[Timestamp], + hashes: seq[WakuMessageHash], + maxPageSize: uint, + ascendingOrder: bool, + requestId: string, +): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = + if hashes.len > MaxHashesPerQuery: + return err(fmt"can not attend queries with more than {MaxHashesPerQuery} hashes") let hexHashes = hashes.mapIt(toHex(it)) + if cursor.isNone() and pubsubTopic.isNone() and contentTopics.len == 0 and + startTime.isNone() and endTime.isNone() and hexHashes.len > 0: + return await self.getMessagesByMessageHashes( + "'" & hexHashes.join("','") & "'", maxPageSize, requestId + ) + if contentTopics.len > 0 and hexHashes.len > 0 and pubsubTopic.isSome() and startTime.isSome() and endTime.isSome(): ## Considered the most common query. Therefore, we use prepared statements to optimize it. if includeData: - return await s.getMessagesPreparedStmt( + return await self.getMessagesPreparedStmt( contentTopics.join(","), PubsubTopic(pubsubTopic.get()), cursor, @@ -765,9 +843,10 @@ method getMessages*( hexHashes.join(","), maxPageSize, ascendingOrder, + requestId, ) else: - return await s.getMessageHashesPreparedStmt( + return await self.getMessageHashesPreparedStmt( contentTopics.join(","), PubsubTopic(pubsubTopic.get()), cursor, @@ -776,27 +855,56 @@ method getMessages*( hexHashes.join(","), maxPageSize, ascendingOrder, + requestId, ) else: if includeData: ## We will run atypical query. In this case we don't use prepared statemets - return await s.getMessagesArbitraryQuery( + return await self.getMessagesArbitraryQuery( contentTopics, pubsubTopic, cursor, startTime, endTime, hexHashes, maxPageSize, - ascendingOrder, + ascendingOrder, requestId, ) else: - return await s.getMessageHashesArbitraryQuery( + return await self.getMessageHashesArbitraryQuery( contentTopics, pubsubTopic, cursor, startTime, endTime, hexHashes, maxPageSize, - ascendingOrder, + ascendingOrder, requestId, ) +method getMessages*( + s: PostgresDriver, + includeData = true, + contentTopics = newSeq[ContentTopic](0), + pubsubTopic = none(PubsubTopic), + cursor = none(ArchiveCursor), + startTime = none(Timestamp), + endTime = none(Timestamp), + hashes = newSeq[WakuMessageHash](0), + maxPageSize = DefaultPageSize, + ascendingOrder = true, + requestId = "", +): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = + let rows = collect(newSeq): + for i in countup(0, hashes.len, MaxHashesPerQuery): + let stop = min(i + MaxHashesPerQuery, hashes.len) + + let splittedHashes = hashes[i ..< stop] + + let subRows = + ?await s.getMessagesWithinLimits( + includeData, contentTopics, pubsubTopic, cursor, startTime, endTime, + splittedHashes, maxPageSize, ascendingOrder, requestId, + ) + + for row in subRows: + row + + return ok(rows) + proc getStr( s: PostgresDriver, query: string ): Future[ArchiveDriverResult[string]] {.async.} = # Performs a query that is expected to return a single string - debug "beginning of getStr" - var ret: string proc rowCallback(pqResult: ptr PGresult) = if pqResult.pqnfields() != 1: @@ -819,7 +927,6 @@ proc getInt( ): Future[ArchiveDriverResult[int64]] {.async.} = # Performs a query that is expected to return a single numeric value (int64) - debug "beginning of getInt" var retInt = 0'i64 let str = (await s.getStr(query)).valueOr: return err("could not get str in getInt: " & $error) @@ -837,8 +944,6 @@ proc getInt( method getDatabaseSize*( s: PostgresDriver ): Future[ArchiveDriverResult[int64]] {.async.} = - debug "beginning of getDatabaseSize" - let intRes = (await s.getInt("SELECT pg_database_size(current_database())")).valueOr: return err("error in getDatabaseSize: " & error) @@ -848,8 +953,6 @@ method getDatabaseSize*( method getMessagesCount*( s: PostgresDriver ): Future[ArchiveDriverResult[int64]] {.async.} = - debug "beginning of getMessagesCount" - let intRes = await s.getInt("SELECT COUNT(1) FROM messages") if intRes.isErr(): return err("error in getMessagesCount: " & intRes.error) @@ -862,8 +965,6 @@ method getOldestMessageTimestamp*( ## In some cases it could happen that we have ## empty partitions which are older than the current stored rows. ## In those cases we want to consider those older partitions as the oldest considered timestamp. - debug "beginning of getOldestMessageTimestamp" - let oldestPartition = s.partitionMngr.getOldestPartition().valueOr: return err("could not get oldest partition: " & $error) @@ -879,7 +980,6 @@ method getOldestMessageTimestamp*( method getNewestMessageTimestamp*( s: PostgresDriver ): Future[ArchiveDriverResult[Timestamp]] {.async.} = - debug "beginning of getNewestMessageTimestamp" let intRes = await s.getInt("SELECT MAX(timestamp) FROM messages") if intRes.isErr(): @@ -890,9 +990,7 @@ method getNewestMessageTimestamp*( method deleteOldestMessagesNotWithinLimit*( s: PostgresDriver, limit: int ): Future[ArchiveDriverResult[void]] {.async.} = - debug "beginning of deleteOldestMessagesNotWithinLimit" - - let execRes = await s.writeConnPool.pgQuery( + var execRes = await s.writeConnPool.pgQuery( """DELETE FROM messages WHERE messageHash NOT IN ( SELECT messageHash FROM messages ORDER BY timestamp DESC LIMIT ? @@ -902,15 +1000,28 @@ method deleteOldestMessagesNotWithinLimit*( if execRes.isErr(): return err("error in deleteOldestMessagesNotWithinLimit: " & execRes.error) - debug "end of deleteOldestMessagesNotWithinLimit" + execRes = await s.writeConnPool.pgQuery( + """DELETE FROM messages_lookup WHERE messageHash NOT IN + ( + SELECT messageHash FROM messages ORDER BY timestamp DESC LIMIT ? + );""", + @[$limit], + ) + if execRes.isErr(): + return err( + "error in deleteOldestMessagesNotWithinLimit messages_lookup: " & execRes.error + ) + return ok() method close*(s: PostgresDriver): Future[ArchiveDriverResult[void]] {.async.} = - debug "beginning of postgres close" - ## Cancel the partition factory loop s.futLoopPartitionFactory.cancelSoon() + ## Cancel analyze table loop + if not s.futLoopAnalyzeTable.isNil(): + s.futLoopAnalyzeTable.cancelSoon() + ## Close the database connection let writeCloseRes = await s.writeConnPool.close() let readCloseRes = await s.readConnPool.close() @@ -944,6 +1055,7 @@ proc sleep*( return ok() +const EXPECTED_LOCK_ERROR* = "another waku instance is currently executing a migration" proc acquireDatabaseLock*( s: PostgresDriver, lockId: int = 841886 ): Future[ArchiveDriverResult[void]] {.async.} = @@ -954,8 +1066,6 @@ proc acquireDatabaseLock*( ## "performWriteQueryWithLock" in the migrations process because we can't nest two PL/SQL ## scripts. - debug "beginning of acquireDatabaseLock", lockId - let locked = ( await s.getStr( fmt""" @@ -966,7 +1076,7 @@ proc acquireDatabaseLock*( return err("error acquiring a lock: " & error) if locked == "f": - return err("another waku instance is currently executing a migration") + return err(EXPECTED_LOCK_ERROR) return ok() @@ -974,7 +1084,6 @@ proc releaseDatabaseLock*( s: PostgresDriver, lockId: int = 841886 ): Future[ArchiveDriverResult[void]] {.async.} = ## Release an advisory lock (useful to avoid more than one application running migrations at the same time) - debug "beginning of releaseDatabaseLock", lockId let unlocked = ( await s.getStr( fmt""" @@ -1001,11 +1110,10 @@ proc performWriteQuery*( const COULD_NOT_ACQUIRE_ADVISORY_LOCK* = "could not acquire advisory lock" -proc performWriteQueryWithLock*( +proc performWriteQueryWithLock( self: PostgresDriver, queryToProtect: string ): Future[ArchiveDriverResult[void]] {.async.} = ## This wraps the original query in a script so that we make sure a pg_advisory lock protects it - debug "performWriteQueryWithLock", queryToProtect let query = fmt""" DO $$ @@ -1068,8 +1176,6 @@ proc addPartition( ## Creates a partition table that will store the messages that fall in the range ## `startTime` <= timestamp < `startTime + duration`. ## `startTime` is measured in seconds since epoch - debug "beginning of addPartition" - let beginning = startTime let `end` = partitions_manager.calcEndPartitionTime(startTime) @@ -1211,8 +1317,6 @@ proc getTableSize*( ): Future[ArchiveDriverResult[string]] {.async.} = ## Returns a human-readable representation of the size for the requested table. ## tableName - table of interest. - debug "beginning of getTableSize" - let tableSize = ( await self.getStr( fmt""" @@ -1226,8 +1330,12 @@ proc getTableSize*( return ok(tableSize) proc removePartition( - self: PostgresDriver, partitionName: string + self: PostgresDriver, partition: Partition ): Future[ArchiveDriverResult[void]] {.async.} = + ## Removes the desired partition and also removes the rows from messages_lookup table + ## whose rows belong to the partition time range + + let partitionName = partition.getName() debug "beginning of removePartition", partitionName var partSize = "" @@ -1240,7 +1348,16 @@ proc removePartition( "ALTER TABLE messages DETACH PARTITION " & partitionName & " CONCURRENTLY;" debug "removeOldestPartition", query = detachPartitionQuery (await self.performWriteQuery(detachPartitionQuery)).isOkOr: - return err(fmt"error in {detachPartitionQuery}: " & $error) + if ($error).contains("FINALIZE"): + ## We assume the database is suggesting to use FINALIZE when detaching a partition + let detachPartitionFinalizeQuery = + "ALTER TABLE messages DETACH PARTITION " & partitionName & " FINALIZE;" + debug "removeOldestPartition detaching with FINALIZE", + query = detachPartitionFinalizeQuery + (await self.performWriteQuery(detachPartitionFinalizeQuery)).isOkOr: + return err(fmt"error in FINALIZE {detachPartitionFinalizeQuery}: " & $error) + else: + return err(fmt"error in {detachPartitionQuery}: " & $error) ## Drop the partition let dropPartitionQuery = "DROP TABLE " & partitionName @@ -1251,6 +1368,13 @@ proc removePartition( debug "removed partition", partition_name = partitionName, partition_size = partSize self.partitionMngr.removeOldestPartitionName() + ## Now delete rows from the messages_lookup table + let timeRange = partition.getTimeRange() + let `end` = timeRange.`end` * 1_000_000_000 + let deleteRowsQuery = "DELETE FROM messages_lookup WHERE timestamp < " & $`end` + (await self.performWriteQuery(deleteRowsQuery)).isOkOr: + return err(fmt"error in {deleteRowsQuery}: " & $error) + return ok() proc removePartitionsOlderThan( @@ -1265,7 +1389,7 @@ proc removePartitionsOlderThan( return err("could not get oldest partition in removePartitionOlderThan: " & $error) while not oldestPartition.containsMoment(tsInSec): - (await self.removePartition(oldestPartition.getName())).isOkOr: + (await self.removePartition(oldestPartition)).isOkOr: return err("issue in removePartitionsOlderThan: " & $error) oldestPartition = self.partitionMngr.getOldestPartition().valueOr: @@ -1280,8 +1404,6 @@ proc removeOldestPartition( self: PostgresDriver, forceRemoval: bool = false, ## To allow cleanup in tests ): Future[ArchiveDriverResult[void]] {.async.} = ## Indirectly called from the retention policy - debug "beginning of removeOldestPartition" - let oldestPartition = self.partitionMngr.getOldestPartition().valueOr: return err("could not remove oldest partition: " & $error) @@ -1295,7 +1417,7 @@ proc removeOldestPartition( debug "Skipping to remove the current partition" return ok() - return await self.removePartition(oldestPartition.getName()) + return await self.removePartition(oldestPartition) proc containsAnyPartition*(self: PostgresDriver): bool = return not self.partitionMngr.isEmpty() @@ -1303,8 +1425,6 @@ proc containsAnyPartition*(self: PostgresDriver): bool = method decreaseDatabaseSize*( driver: PostgresDriver, targetSizeInBytes: int64, forceRemoval: bool = false ): Future[ArchiveDriverResult[void]] {.async.} = - debug "beginning of decreaseDatabaseSize" - var dbSize = (await driver.getDatabaseSize()).valueOr: return err("decreaseDatabaseSize failed to get database size: " & $error) @@ -1371,8 +1491,6 @@ method existsTable*( proc getCurrentVersion*( s: PostgresDriver ): Future[ArchiveDriverResult[int64]] {.async.} = - debug "beginning of getCurrentVersion" - let existsVersionTable = (await s.existsTable("version")).valueOr: return err("error in getCurrentVersion-existsTable: " & $error) @@ -1389,8 +1507,6 @@ method deleteMessagesOlderThanTimestamp*( ): Future[ArchiveDriverResult[void]] {.async.} = ## First of all, let's remove the older partitions so that we can reduce ## the database size. - debug "beginning of deleteMessagesOlderThanTimestamp" - (await s.removePartitionsOlderThan(tsNanoSec)).isOkOr: return err("error while removing older partitions: " & $error) @@ -1402,3 +1518,36 @@ method deleteMessagesOlderThanTimestamp*( return err("error in deleteMessagesOlderThanTimestamp: " & $error) return ok() + +############################################ +## TODO: start splitting code better + +const AnalyzeQuery = "ANALYZE messages" +const AnalyzeTableLockId = 111111 ## An arbitrary and different lock id +const RunAnalyzeInterval = timer.days(1) + +proc analyzeTableLoop(self: PostgresDriver) {.async.} = + ## The database stats should be calculated regularly so that the planner + ## picks up the proper indexes and we have better query performance. + while true: + debug "analyzeTableLoop lock db" + (await self.acquireDatabaseLock(AnalyzeTableLockId)).isOkOr: + if error != EXPECTED_LOCK_ERROR: + error "failed to acquire lock in analyzeTableLoop", error = error + await sleepAsync(RunAnalyzeInterval) + continue + + debug "analyzeTableLoop start analysis" + (await self.performWriteQuery(AnalyzeQuery)).isOkOr: + error "failed to run ANALYZE messages", error = error + + debug "analyzeTableLoop unlock db" + (await self.releaseDatabaseLock(AnalyzeTableLockId)).isOkOr: + error "failed to release lock analyzeTableLoop", error = error + + debug "analyzeTableLoop analysis completed" + + await sleepAsync(RunAnalyzeInterval) + +proc startAnalyzeTableLoop*(self: PostgresDriver) = + self.futLoopAnalyzeTable = self.analyzeTableLoop diff --git a/waku/waku_archive/driver/queue_driver/queue_driver.nim b/waku/waku_archive/driver/queue_driver/queue_driver.nim index df21cf8f4a..9dbf3c112f 100644 --- a/waku/waku_archive/driver/queue_driver/queue_driver.nim +++ b/waku/waku_archive/driver/queue_driver/queue_driver.nim @@ -256,6 +256,7 @@ method getMessages*( hashes: seq[WakuMessageHash] = @[], maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = var index = none(Index) diff --git a/waku/waku_archive/driver/sqlite_driver/queries.nim b/waku/waku_archive/driver/sqlite_driver/queries.nim index 0a167937ec..6fafc06ebe 100644 --- a/waku/waku_archive/driver/sqlite_driver/queries.nim +++ b/waku/waku_archive/driver/sqlite_driver/queries.nim @@ -82,8 +82,7 @@ proc createTable*(db: SqliteDatabase): DatabaseResult[void] = ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -98,8 +97,7 @@ proc createOldestMessageTimestampIndex*(db: SqliteDatabase): DatabaseResult[void ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -184,8 +182,7 @@ proc deleteMessagesOlderThanTimestamp*( ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -206,8 +203,7 @@ proc deleteOldestMessagesNotWithinLimit*( ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() diff --git a/waku/waku_archive/driver/sqlite_driver/sqlite_driver.nim b/waku/waku_archive/driver/sqlite_driver/sqlite_driver.nim index d872b9f153..173dd3e813 100644 --- a/waku/waku_archive/driver/sqlite_driver/sqlite_driver.nim +++ b/waku/waku_archive/driver/sqlite_driver/sqlite_driver.nim @@ -83,6 +83,7 @@ method getMessages*( hashes = newSeq[WakuMessageHash](0), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = if not includeData: return s.db.selectMessageHashesByStoreQueryWithLimit( diff --git a/waku/waku_archive_legacy/archive.nim b/waku/waku_archive_legacy/archive.nim index 690257ff59..f130ffc5f2 100644 --- a/waku/waku_archive_legacy/archive.nim +++ b/waku/waku_archive_legacy/archive.nim @@ -151,6 +151,7 @@ proc findMessages*( hashes = query.hashes, maxPageSize = maxPageSize + 1, ascendingOrder = isAscendingOrder, + requestId = query.requestId, ) ).valueOr: return err(ArchiveError(kind: ArchiveErrorKind.DRIVER_ERROR, cause: error)) @@ -230,6 +231,7 @@ proc findMessagesV2*( endTime = query.endTime, maxPageSize = maxPageSize + 1, ascendingOrder = isAscendingOrder, + requestId = query.requestId, ) ).valueOr: return err(ArchiveError(kind: ArchiveErrorKind.DRIVER_ERROR, cause: error)) diff --git a/waku/waku_archive_legacy/common.nim b/waku/waku_archive_legacy/common.nim index 9ef67178f9..e068e0f0c5 100644 --- a/waku/waku_archive_legacy/common.nim +++ b/waku/waku_archive_legacy/common.nim @@ -52,6 +52,7 @@ type hashes*: seq[WakuMessageHash] pageSize*: uint direction*: PagingDirection + requestId*: string ArchiveResponse* = object hashes*: seq[WakuMessageHash] diff --git a/waku/waku_archive_legacy/driver.nim b/waku/waku_archive_legacy/driver.nim index 98dccdf0ad..8ff8df029a 100644 --- a/waku/waku_archive_legacy/driver.nim +++ b/waku/waku_archive_legacy/driver.nim @@ -41,6 +41,7 @@ method getMessagesV2*( endTime = none(Timestamp), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.base, deprecated, async.} = discard @@ -55,6 +56,7 @@ method getMessages*( hashes = newSeq[WakuMessageHash](0), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.base, async.} = discard diff --git a/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim b/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim index 8f76c4d2da..e8eed42386 100644 --- a/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim +++ b/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim @@ -15,6 +15,7 @@ import ../../../waku_core, ../../common, ../../driver, + ./postgres_healthcheck, ../../../common/databases/db_postgres as waku_postgres type PostgresDriver* = ref object of ArchiveDriver @@ -27,6 +28,10 @@ const InsertRowStmtDefinition = # TODO: get the sql queries from a file """INSERT INTO messages (id, messageHash, contentTopic, payload, pubsubTopic, version, timestamp, meta) VALUES ($1, $2, $3, $4, $5, $6, $7, CASE WHEN $8 = '' THEN NULL ELSE $8 END) ON CONFLICT DO NOTHING;""" +const InsertRowInMessagesLookupStmtName = "InsertRowMessagesLookup" +const InsertRowInMessagesLookupStmtDefinition = + """INSERT INTO messages_lookup (messageHash, timestamp) VALUES ($1, $2) ON CONFLICT DO NOTHING;""" + const SelectNoCursorAscStmtName = "SelectWithoutCursorAsc" const SelectNoCursorAscStmtDef = """SELECT contentTopic, payload, pubsubTopic, version, timestamp, id, messageHash, meta FROM messages @@ -128,6 +133,12 @@ proc new*( let writeConnPool = PgAsyncPool.new(dbUrl, maxNumConnOnEachPool).valueOr: return err("error creating write conn pool PgAsyncPool") + if not isNil(onFatalErrorAction): + asyncSpawn checkConnectivity(readConnPool, onFatalErrorAction) + + if not isNil(onFatalErrorAction): + asyncSpawn checkConnectivity(writeConnPool, onFatalErrorAction) + let driver = PostgresDriver(writeConnPool: writeConnPool, readConnPool: readConnPool) return ok(driver) @@ -212,28 +223,42 @@ method put*( trace "put PostgresDriver", timestamp = timestamp + ( + await s.writeConnPool.runStmt( + InsertRowStmtName, + InsertRowStmtDefinition, + @[ + digest, messageHash, contentTopic, payload, pubsubTopic, version, timestamp, + meta, + ], + @[ + int32(digest.len), + int32(messageHash.len), + int32(contentTopic.len), + int32(payload.len), + int32(pubsubTopic.len), + int32(version.len), + int32(timestamp.len), + int32(meta.len), + ], + @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], + ) + ).isOkOr: + return err("could not put msg in messages table: " & $error) + + ## Now add the row to messages_lookup return await s.writeConnPool.runStmt( - InsertRowStmtName, - InsertRowStmtDefinition, - @[digest, messageHash, contentTopic, payload, pubsubTopic, version, timestamp, meta], - @[ - int32(digest.len), - int32(messageHash.len), - int32(contentTopic.len), - int32(payload.len), - int32(pubsubTopic.len), - int32(version.len), - int32(timestamp.len), - int32(meta.len), - ], - @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], + InsertRowInMessagesLookupStmtName, + InsertRowInMessagesLookupStmtDefinition, + @[messageHash, timestamp], + @[int32(messageHash.len), int32(timestamp.len)], + @[int32(0), int32(0)], ) method getAllMessages*( s: PostgresDriver ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## Retrieve all messages from the store. - var rows: seq[(PubsubTopic, WakuMessage, seq[byte], Timestamp, WakuMessageHash)] proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) @@ -261,6 +286,7 @@ proc getMessagesArbitraryQuery( hexHashes: seq[string] = @[], maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## This proc allows to handle atypical queries. We don't use prepared statements for those. @@ -300,6 +326,7 @@ proc getMessagesArbitraryQuery( @[int32(hashHex.len)], @[int32(0)], entreeCallback, + requestId, ) ).isOkOr: return err("failed to run query with cursor: " & $error) @@ -340,7 +367,7 @@ proc getMessagesArbitraryQuery( proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) - (await s.readConnPool.pgQuery(query, args, rowCallback)).isOkOr: + (await s.readConnPool.pgQuery(query, args, rowCallback, requestId)).isOkOr: return err("failed to run query: " & $error) return ok(rows) @@ -354,6 +381,7 @@ proc getMessagesV2ArbitraryQuery( endTime = none(Timestamp), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async, deprecated.} = ## This proc allows to handle atypical queries. We don't use prepared statements for those. @@ -404,7 +432,7 @@ proc getMessagesV2ArbitraryQuery( proc rowCallback(pqResult: ptr PGresult) = rowCallbackImpl(pqResult, rows) - (await s.readConnPool.pgQuery(query, args, rowCallback)).isOkOr: + (await s.readConnPool.pgQuery(query, args, rowCallback, requestId)).isOkOr: return err("failed to run query: " & $error) return ok(rows) @@ -419,6 +447,7 @@ proc getMessagesPreparedStmt( hashes: string, maxPageSize = DefaultPageSize, ascOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = ## This proc aims to run the most typical queries in a more performant way, i.e. by means of ## prepared statements. @@ -449,6 +478,7 @@ proc getMessagesPreparedStmt( @[int32(hash.len)], @[int32(0)], entreeCallback, + requestId, ) ).isOkOr: return err("failed to run query with cursor: " & $error) @@ -485,6 +515,7 @@ proc getMessagesPreparedStmt( int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0) ], rowCallback, + requestId, ) ).isOkOr: return err("failed to run query with cursor: " & $error) @@ -508,6 +539,7 @@ proc getMessagesPreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err("failed to run query without cursor: " & $error) @@ -523,6 +555,7 @@ proc getMessagesV2PreparedStmt( endTime: Timestamp, maxPageSize = DefaultPageSize, ascOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async, deprecated.} = ## This proc aims to run the most typical queries in a more performant way, i.e. by means of ## prepared statements. @@ -562,6 +595,7 @@ proc getMessagesV2PreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err("failed to run query with cursor: " & $error) @@ -585,12 +619,59 @@ proc getMessagesV2PreparedStmt( ], @[int32(0), int32(0), int32(0), int32(0), int32(0)], rowCallback, + requestId, ) ).isOkOr: return err("failed to run query without cursor: " & $error) return ok(rows) +proc getMessagesByMessageHashes( + s: PostgresDriver, hashes: string, maxPageSize: uint, requestId: string +): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = + ## Retrieves information only filtering by a given messageHashes list. + ## This proc levarages on the messages_lookup table to have better query performance + ## and only query the desired partitions in the partitioned messages table + var query = + fmt""" + WITH min_timestamp AS ( + SELECT MIN(timestamp) AS min_ts + FROM messages_lookup + WHERE messagehash IN ( + {hashes} + ) + ) + SELECT contentTopic, payload, pubsubTopic, version, m.timestamp, id, m.messageHash, meta + FROM messages m + INNER JOIN + messages_lookup l + ON + m.timestamp = l.timestamp + AND m.messagehash = l.messagehash + WHERE + l.timestamp >= (SELECT min_ts FROM min_timestamp) + AND l.messagehash IN ( + {hashes} + ) + ORDER BY + m.timestamp DESC, + m.messagehash DESC + LIMIT {maxPageSize}; + """ + + var rows: seq[(PubsubTopic, WakuMessage, seq[byte], Timestamp, WakuMessageHash)] + proc rowCallback(pqResult: ptr PGresult) = + rowCallbackImpl(pqResult, rows) + + ( + await s.readConnPool.pgQuery( + query = query, rowCallback = rowCallback, requestId = requestId + ) + ).isOkOr: + return err("failed to run query: " & $error) + + return ok(rows) + method getMessages*( s: PostgresDriver, includeData = true, @@ -602,9 +683,16 @@ method getMessages*( hashes = newSeq[WakuMessageHash](0), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = let hexHashes = hashes.mapIt(toHex(it)) + if cursor.isNone() and pubsubTopic.isNone() and contentTopicSeq.len == 0 and + startTime.isNone() and endTime.isNone() and hexHashes.len > 0: + return await s.getMessagesByMessageHashes( + "'" & hexHashes.join("','") & "'", maxPageSize, requestId + ) + if contentTopicSeq.len == 1 and hexHashes.len == 1 and pubsubTopic.isSome() and startTime.isSome() and endTime.isSome(): ## Considered the most common query. Therefore, we use prepared statements to optimize it. @@ -617,12 +705,13 @@ method getMessages*( hexHashes.join(","), maxPageSize, ascendingOrder, + requestId, ) else: ## We will run atypical query. In this case we don't use prepared statemets return await s.getMessagesArbitraryQuery( contentTopicSeq, pubsubTopic, cursor, startTime, endTime, hexHashes, maxPageSize, - ascendingOrder, + ascendingOrder, requestId, ) method getMessagesV2*( @@ -634,6 +723,7 @@ method getMessagesV2*( endTime = none(Timestamp), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async, deprecated.} = if contentTopicSeq.len == 1 and pubsubTopic.isSome() and startTime.isSome() and endTime.isSome(): @@ -646,12 +736,13 @@ method getMessagesV2*( endTime.get(), maxPageSize, ascendingOrder, + requestId, ) else: ## We will run atypical query. In this case we don't use prepared statemets return await s.getMessagesV2ArbitraryQuery( contentTopicSeq, pubsubTopic, cursor, startTime, endTime, maxPageSize, - ascendingOrder, + ascendingOrder, requestId, ) proc getStr( diff --git a/waku/waku_archive_legacy/driver/postgres_driver/postgres_healthcheck.nim b/waku/waku_archive_legacy/driver/postgres_driver/postgres_healthcheck.nim new file mode 100644 index 0000000000..4c9f170c9f --- /dev/null +++ b/waku/waku_archive_legacy/driver/postgres_driver/postgres_healthcheck.nim @@ -0,0 +1,38 @@ +{.push raises: [].} + +import chronos, chronicles, results +import ../../../common/databases/db_postgres, ../../../common/error_handling + +## Simple query to validate that the postgres is working and attending requests +const HealthCheckQuery = "SELECT version();" +const CheckConnectivityInterval = 60.seconds +const MaxNumTrials = 20 +const TrialInterval = 1.seconds + +proc checkConnectivity*( + connPool: PgAsyncPool, onFatalErrorAction: OnFatalErrorHandler +) {.async.} = + while true: + (await connPool.pgQuery(HealthCheckQuery)).isOkOr: + ## The connection failed once. Let's try reconnecting for a while. + ## Notice that the 'pgQuery' proc tries to establish a new connection. + + block errorBlock: + ## Force close all the opened connections. No need to close gracefully. + (await connPool.resetConnPool()).isOkOr: + onFatalErrorAction("checkConnectivity legacy resetConnPool error: " & error) + + var numTrial = 0 + while numTrial < MaxNumTrials: + let res = await connPool.pgQuery(HealthCheckQuery) + if res.isOk(): + ## Connection resumed. Let's go back to the normal healthcheck. + break errorBlock + + await sleepAsync(TrialInterval) + numTrial.inc() + + ## The connection couldn't be resumed. Let's inform the upper layers. + onFatalErrorAction("postgres legacy health check error: " & error) + + await sleepAsync(CheckConnectivityInterval) diff --git a/waku/waku_archive_legacy/driver/queue_driver/queue_driver.nim b/waku/waku_archive_legacy/driver/queue_driver/queue_driver.nim index 85c30823a8..942a720dfc 100644 --- a/waku/waku_archive_legacy/driver/queue_driver/queue_driver.nim +++ b/waku/waku_archive_legacy/driver/queue_driver/queue_driver.nim @@ -268,6 +268,7 @@ method getMessages*( hashes: seq[WakuMessageHash] = @[], maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = let cursor = cursor.map(toIndex) diff --git a/waku/waku_archive_legacy/driver/sqlite_driver/queries.nim b/waku/waku_archive_legacy/driver/sqlite_driver/queries.nim index 76d9755e56..47f1d86ae7 100644 --- a/waku/waku_archive_legacy/driver/sqlite_driver/queries.nim +++ b/waku/waku_archive_legacy/driver/sqlite_driver/queries.nim @@ -96,8 +96,7 @@ proc createTable*(db: SqliteDatabase): DatabaseResult[void] = ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -112,8 +111,7 @@ proc createOldestMessageTimestampIndex*(db: SqliteDatabase): DatabaseResult[void ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -127,8 +125,7 @@ proc createHistoryQueryIndex*(db: SqliteDatabase): DatabaseResult[void] = ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -226,8 +223,7 @@ proc deleteMessagesOlderThanTimestamp*( ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() @@ -248,8 +244,7 @@ proc deleteOldestMessagesNotWithinLimit*( ?db.query( query, proc(s: ptr sqlite3_stmt) = - discard - , + discard, ) return ok() diff --git a/waku/waku_archive_legacy/driver/sqlite_driver/sqlite_driver.nim b/waku/waku_archive_legacy/driver/sqlite_driver/sqlite_driver.nim index 4e0450aabc..5a6c12b05a 100644 --- a/waku/waku_archive_legacy/driver/sqlite_driver/sqlite_driver.nim +++ b/waku/waku_archive_legacy/driver/sqlite_driver/sqlite_driver.nim @@ -93,9 +93,8 @@ method getMessagesV2*( endTime = none(Timestamp), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId: string, ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async, deprecated.} = - echo "here" - let cursor = cursor.map(toDbCursor) let rowsRes = s.db.selectMessagesByHistoryQueryWithLimit( @@ -121,6 +120,7 @@ method getMessages*( hashes = newSeq[WakuMessageHash](0), maxPageSize = DefaultPageSize, ascendingOrder = true, + requestId = "", ): Future[ArchiveDriverResult[seq[ArchiveRow]]] {.async.} = let cursor = if cursor.isSome(): diff --git a/waku/waku_core/message/digest.nim b/waku/waku_core/message/digest.nim index 1d4d122816..cb4f5b0141 100644 --- a/waku/waku_core/message/digest.nim +++ b/waku/waku_core/message/digest.nim @@ -8,6 +8,17 @@ import ../topics, ./message type WakuMessageHash* = array[32, byte] +func shortLog*(hash: WakuMessageHash): string = + ## Returns compact string representation of ``WakuMessageHash``. + var hexhash = newStringOfCap(13) + hexhash &= hash.toOpenArray(0, 1).to0xHex() + hexhash &= "..." + hexhash &= hash.toOpenArray(hash.len - 2, hash.high).toHex() + hexhash + +func `$`*(hash: WakuMessageHash): string = + shortLog(hash) + const EmptyWakuMessageHash*: WakuMessageHash = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/waku/waku_core/time.nim b/waku/waku_core/time.nim index c8aa563558..bee5522bd7 100644 --- a/waku/waku_core/time.nim +++ b/waku/waku_core/time.nim @@ -18,7 +18,9 @@ proc nowInUnixFloat(): float = proc getNowInNanosecondTime*(): Timestamp = return getNanosecondTime(nowInUnixFloat()) -template nanosecondTime*(collector: Summary | Histogram, body: untyped) = +template nanosecondTime*( + collector: Summary | Histogram | typedesc[IgnoredCollector], body: untyped +) = when defined(metrics): let start = nowInUnixFloat() body @@ -33,3 +35,16 @@ template nanosecondTime*(collector: Gauge, body: untyped) = metrics.set(collector, nowInUnixFloat() - start) else: body + +# Unused yet. Kept for future use in Waku Sync. +#[ proc timestampInSeconds*(time: Timestamp): Timestamp = + let timeStr = $time + var timestamp: Timestamp = time + + if timeStr.len() > 16: + timestamp = Timestamp(time div Timestamp(1_000_000_000)) + elif timeStr.len() < 16 and timeStr.len() > 13: + timestamp = Timestamp(time div Timestamp(1_000_000)) + elif timeStr.len() > 10: + timestamp = Timestamp(time div Timestamp(1000)) + return timestamp ]# diff --git a/waku/waku_core/topics/pubsub_topic.nim b/waku/waku_core/topics/pubsub_topic.nim index 6306df081f..27ea271804 100644 --- a/waku/waku_core/topics/pubsub_topic.nim +++ b/waku/waku_core/topics/pubsub_topic.nim @@ -13,24 +13,26 @@ export parsing type PubsubTopic* = string -const DefaultPubsubTopic* = PubsubTopic("/waku/2/rs/0/0") +## Relay Shard -## Namespaced pub-sub topic - -type NsPubsubTopic* = object +type RelayShard* = object clusterId*: uint16 shardId*: uint16 -proc staticSharding*(T: type NsPubsubTopic, clusterId, shardId: uint16): T = - return NsPubsubTopic(clusterId: clusterId, shardId: shardId) +const DefaultShardId* = uint16(0) +const DefaultClusterId* = uint16(0) +const DefaultRelayShard* = + RelayShard(clusterId: DefaultClusterId, shardId: DefaultShardId) # Serialization -proc `$`*(topic: NsPubsubTopic): string = +proc `$`*(topic: RelayShard): string = ## Returns a string representation of a namespaced topic ## in the format `/waku/2/rs// return "/waku/2/rs/" & $topic.clusterId & "/" & $topic.shardId +const DefaultPubsubTopic* = $DefaultRelayShard + # Deserialization const @@ -38,8 +40,8 @@ const StaticShardingPubsubTopicPrefix = Waku2PubsubTopicPrefix & "/rs" proc parseStaticSharding*( - T: type NsPubsubTopic, topic: PubsubTopic -): ParsingResult[NsPubsubTopic] = + T: type RelayShard, topic: PubsubTopic +): ParsingResult[RelayShard] = if not topic.startsWith(StaticShardingPubsubTopicPrefix): return err( ParsingError.invalidFormat("must start with " & StaticShardingPubsubTopicPrefix) @@ -67,19 +69,19 @@ proc parseStaticSharding*( ParsingError.invalidFormat($err) ) - ok(NsPubsubTopic.staticSharding(clusterId, shardId)) + ok(RelayShard(clusterId: clusterId, shardId: shardId)) -proc parse*(T: type NsPubsubTopic, topic: PubsubTopic): ParsingResult[NsPubsubTopic] = +proc parse*(T: type RelayShard, topic: PubsubTopic): ParsingResult[RelayShard] = ## Splits a namespaced topic string into its constituent parts. ## The topic string has to be in the format `////` - NsPubsubTopic.parseStaticSharding(topic) + RelayShard.parseStaticSharding(topic) # Pubsub topic compatibility -converter toPubsubTopic*(topic: NsPubsubTopic): PubsubTopic = +converter toPubsubTopic*(topic: RelayShard): PubsubTopic = $topic -proc `==`*[T: NsPubsubTopic](x, y: T): bool = +proc `==`*[T: RelayShard](x, y: T): bool = if x.clusterId != y.clusterId: return false diff --git a/waku/waku_core/topics/sharding.nim b/waku/waku_core/topics/sharding.nim index 6c7b172d57..4a4af4cb5d 100644 --- a/waku/waku_core/topics/sharding.nim +++ b/waku/waku_core/topics/sharding.nim @@ -16,7 +16,7 @@ type Sharding* = object proc new*(T: type Sharding, clusterId: uint16, shardCount: uint32): T = return Sharding(clusterId: clusterId, shardCountGenZero: shardCount) -proc getGenZeroShard*(s: Sharding, topic: NsContentTopic, count: int): NsPubsubTopic = +proc getGenZeroShard*(s: Sharding, topic: NsContentTopic, count: int): RelayShard = let bytes = toBytes(topic.application) & toBytes(topic.version) let hash = sha256.digest(bytes) @@ -27,9 +27,9 @@ proc getGenZeroShard*(s: Sharding, topic: NsContentTopic, count: int): NsPubsubT # This is equilavent to modulo shard count but faster let shard = hashValue and uint64((count - 1)) - NsPubsubTopic.staticSharding(s.clusterId, uint16(shard)) + RelayShard(clusterId: s.clusterId, shardId: uint16(shard)) -proc getShard*(s: Sharding, topic: NsContentTopic): Result[NsPubsubTopic, string] = +proc getShard*(s: Sharding, topic: NsContentTopic): Result[RelayShard, string] = ## Compute the (pubsub topic) shard to use for this content topic. if topic.generation.isNone(): @@ -42,26 +42,26 @@ proc getShard*(s: Sharding, topic: NsContentTopic): Result[NsPubsubTopic, string else: return err("Generation > 0 are not supported yet") -proc getShard*(s: Sharding, topic: ContentTopic): Result[PubsubTopic, string] = +proc getShard*(s: Sharding, topic: ContentTopic): Result[RelayShard, string] = let parsedTopic = NsContentTopic.parse(topic).valueOr: return err($error) let shard = ?s.getShard(parsedTopic) - ok($shard) + ok(shard) proc parseSharding*( s: Sharding, pubsubTopic: Option[PubsubTopic], contentTopics: ContentTopic | seq[ContentTopic], -): Result[Table[NsPubsubTopic, seq[NsContentTopic]], string] = +): Result[Table[RelayShard, seq[NsContentTopic]], string] = var topics: seq[ContentTopic] when contentTopics is seq[ContentTopic]: topics = contentTopics else: topics = @[contentTopics] - var topicMap = initTable[NsPubsubTopic, seq[NsContentTopic]]() + var topicMap = initTable[RelayShard, seq[NsContentTopic]]() for contentTopic in topics: let parseRes = NsContentTopic.parse(contentTopic) @@ -73,7 +73,7 @@ proc parseSharding*( let pubsub = if pubsubTopic.isSome(): - let parseRes = NsPubsubTopic.parse(pubsubTopic.get()) + let parseRes = RelayShard.parse(pubsubTopic.get()) if parseRes.isErr(): return err("Cannot parse pubsub topic: " & $parseRes.error) @@ -97,7 +97,7 @@ proc parseSharding*( ok(topicMap) -#type ShardsPriority = seq[tuple[topic: NsPubsubTopic, value: float64]] +#type ShardsPriority = seq[tuple[topic: RelayShard, value: float64]] #[ proc shardCount*(topic: NsContentTopic): Result[int, string] = ## Returns the total shard count from the content topic. @@ -117,7 +117,7 @@ proc parseSharding*( #[ proc applyWeight(hashValue: uint64, weight: float64): float64 = (-weight) / math.ln(float64(hashValue) / float64(high(uint64))) ]# -#[ proc hashOrder*(x, y: (NsPubsubTopic, float64)): int = +#[ proc hashOrder*(x, y: (RelayShard, float64)): int = cmp(x[1], y[1]) ]# #[ proc weightedShardList*(topic: NsContentTopic, shardCount: int, weightList: seq[float64]): Result[ShardsPriority, string] = @@ -127,10 +127,10 @@ proc parseSharding*( let shardsNWeights = zip(toSeq(0..shardCount), weightList) - var list = newSeq[(NsPubsubTopic, float64)](shardCount) + var list = newSeq[(RelayShard, float64)](shardCount) for (shard, weight) in shardsNWeights: - let pubsub = NsPubsubTopic.staticSharding(ClusterId, uint16(shard)) + let pubsub = RelayShard(clusterId: ClusterId, shardId: uint16(shard)) let clusterBytes = toBytesBE(uint16(ClusterId)) let shardBytes = toBytesBE(uint16(shard)) @@ -145,7 +145,7 @@ proc parseSharding*( ok(list) ]# -#[ proc singleHighestWeigthShard*(topic: NsContentTopic): Result[NsPubsubTopic, string] = +#[ proc singleHighestWeigthShard*(topic: NsContentTopic): Result[RelayShard, string] = let count = ? shardCount(topic) let weights = repeat(1.0, count) diff --git a/waku/waku_enr/capabilities.nim b/waku/waku_enr/capabilities.nim index caec9d969b..b6492dda11 100644 --- a/waku/waku_enr/capabilities.nim +++ b/waku/waku_enr/capabilities.nim @@ -18,8 +18,11 @@ type Store = 1 Filter = 2 Lightpush = 3 + Sync = 4 -func init*(T: type CapabilitiesBitfield, lightpush, filter, store, relay: bool): T = +func init*( + T: type CapabilitiesBitfield, lightpush, filter, store, relay, sync: bool = false +): T = ## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/) var bitfield: uint8 if relay: @@ -30,6 +33,8 @@ func init*(T: type CapabilitiesBitfield, lightpush, filter, store, relay: bool): bitfield.setBit(2) if lightpush: bitfield.setBit(3) + if sync: + bitfield.setBit(4) CapabilitiesBitfield(bitfield) func init*(T: type CapabilitiesBitfield, caps: varargs[Capabilities]): T = diff --git a/waku/waku_enr/sharding.nim b/waku/waku_enr/sharding.nim index 8c7fdb363a..88dc4e200f 100644 --- a/waku/waku_enr/sharding.nim +++ b/waku/waku_enr/sharding.nim @@ -13,7 +13,7 @@ import ../common/enr, ../waku_core logScope: topics = "waku enr sharding" -const MaxShardIndex: uint16 = 1023 +const MaxShardIndex*: uint16 = 1023 const ShardingIndicesListEnrField* = "rs" @@ -24,8 +24,8 @@ type RelayShards* = object clusterId*: uint16 shardIds*: seq[uint16] -func topics*(rs: RelayShards): seq[NsPubsubTopic] = - rs.shardIds.mapIt(NsPubsubTopic.staticSharding(rs.clusterId, it)) +func topics*(rs: RelayShards): seq[RelayShard] = + rs.shardIds.mapIt(RelayShard(clusterId: rs.clusterId, shardId: it)) func init*(T: type RelayShards, clusterId, shardId: uint16): Result[T, string] = if shardId > MaxShardIndex: @@ -61,7 +61,7 @@ func topicsToRelayShards*(topics: seq[string]): Result[Option[RelayShards], stri if topics.len < 1: return ok(none(RelayShards)) - let parsedTopicsRes = topics.mapIt(NsPubsubTopic.parse(it)) + let parsedTopicsRes = topics.mapIt(RelayShard.parse(it)) for res in parsedTopicsRes: if res.isErr(): @@ -80,11 +80,11 @@ func topicsToRelayShards*(topics: seq[string]): Result[Option[RelayShards], stri func contains*(rs: RelayShards, clusterId, shardId: uint16): bool = return rs.clusterId == clusterId and rs.shardIds.contains(shardId) -func contains*(rs: RelayShards, topic: NsPubsubTopic): bool = - return rs.contains(topic.clusterId, topic.shardId) +func contains*(rs: RelayShards, shard: RelayShard): bool = + return rs.contains(shard.clusterId, shard.shardId) func contains*(rs: RelayShards, topic: PubsubTopic): bool = - let parseRes = NsPubsubTopic.parse(topic) + let parseRes = RelayShard.parse(topic) if parseRes.isErr(): return false @@ -227,7 +227,7 @@ proc containsShard*(r: Record, clusterId, shardId: uint16): bool = return false let record = r.toTyped().valueOr: - debug "invalid ENR record", error = error + trace "invalid ENR record", error = error return false let rs = record.relaySharding().valueOr: @@ -235,11 +235,11 @@ proc containsShard*(r: Record, clusterId, shardId: uint16): bool = rs.contains(clusterId, shardId) -proc containsShard*(r: Record, topic: NsPubsubTopic): bool = - return containsShard(r, topic.clusterId, topic.shardId) +proc containsShard*(r: Record, shard: RelayShard): bool = + return containsShard(r, shard.clusterId, shard.shardId) proc containsShard*(r: Record, topic: PubsubTopic): bool = - let parseRes = NsPubsubTopic.parse(topic) + let parseRes = RelayShard.parse(topic) if parseRes.isErr(): debug "invalid static sharding topic", topic = topic, error = parseRes.error return false diff --git a/waku/waku_filter_v2/client.nim b/waku/waku_filter_v2/client.nim index 07b67a8b2e..617648aff8 100644 --- a/waku/waku_filter_v2/client.nim +++ b/waku/waku_filter_v2/client.nim @@ -4,7 +4,13 @@ import std/options, chronicles, chronos, libp2p/protocols/protocol, bearssl/rand import - ../node/peer_manager, ../waku_core, ./common, ./protocol_metrics, ./rpc_codec, ./rpc + ../node/peer_manager, + ../node/delivery_monitor/subscriptions_observer, + ../waku_core, + ./common, + ./protocol_metrics, + ./rpc_codec, + ./rpc logScope: topics = "waku filter client" @@ -13,12 +19,16 @@ type WakuFilterClient* = ref object of LPProtocol rng: ref HmacDrbgContext peerManager: PeerManager pushHandlers: seq[FilterPushHandler] + subscrObservers: seq[SubscriptionObserver] func generateRequestId(rng: ref HmacDrbgContext): string = var bytes: array[10, byte] hmacDrbgGenerate(rng[], bytes) return toHex(bytes) +proc addSubscrObserver*(wfc: WakuFilterClient, obs: SubscriptionObserver) = + wfc.subscrObservers.add(obs) + proc sendSubscribeRequest( wfc: WakuFilterClient, servicePeer: RemotePeerInfo, @@ -113,7 +123,12 @@ proc subscribe*( requestId = requestId, pubsubTopic = pubsubTopic, contentTopics = contentTopicSeq ) - return await wfc.sendSubscribeRequest(servicePeer, filterSubscribeRequest) + ?await wfc.sendSubscribeRequest(servicePeer, filterSubscribeRequest) + + for obs in wfc.subscrObservers: + obs.onSubscribe(pubSubTopic, contentTopicSeq) + + return ok() proc unsubscribe*( wfc: WakuFilterClient, @@ -132,7 +147,12 @@ proc unsubscribe*( requestId = requestId, pubsubTopic = pubsubTopic, contentTopics = contentTopicSeq ) - return await wfc.sendSubscribeRequest(servicePeer, filterSubscribeRequest) + ?await wfc.sendSubscribeRequest(servicePeer, filterSubscribeRequest) + + for obs in wfc.subscrObservers: + obs.onUnsubscribe(pubSubTopic, contentTopicSeq) + + return ok() proc unsubscribeAll*( wfc: WakuFilterClient, servicePeer: RemotePeerInfo diff --git a/waku/waku_filter_v2/protocol.nim b/waku/waku_filter_v2/protocol.nim index 4c39a70a76..695093fe57 100644 --- a/waku/waku_filter_v2/protocol.nim +++ b/waku/waku_filter_v2/protocol.nim @@ -328,6 +328,7 @@ proc new*( ) wf.initProtocolHandler() + setServiceLimitMetric(WakuFilterSubscribeCodec, rateLimitSetting) return wf const MaintainSubscriptionsInterval* = 1.minutes diff --git a/waku/waku_filter_v2/subscriptions.nim b/waku/waku_filter_v2/subscriptions.nim index f8f2987081..7df21ea0f4 100644 --- a/waku/waku_filter_v2/subscriptions.nim +++ b/waku/waku_filter_v2/subscriptions.nim @@ -12,12 +12,6 @@ const DefaultSubscriptionTimeToLiveSec* = 5.minutes MessageCacheTTL* = 2.minutes - # Acceptable call frequence from one peer using filter service - # Assumption is having to set up a subscription with max 30 calls than using ping in every min - # While subscribe/unsubscribe events are distributed in time among clients, pings will happen regularly from - # all subscribed peers - FilterPerPeerRateLimit*: RateLimitSetting = (30, 1.minutes) - type # a single filter criterion is fully defined by a pubsub topic and content topic FilterCriterion* = tuple[pubsubTopic: PubsubTopic, contentTopic: ContentTopic] diff --git a/waku/waku_keystore/utils.nim b/waku/waku_keystore/utils.nim index 422b96106c..88c10207e2 100644 --- a/waku/waku_keystore/utils.nim +++ b/waku/waku_keystore/utils.nim @@ -9,8 +9,7 @@ proc hasKeys*(data: JsonNode, keys: openArray[string]): bool = return all( keys, proc(key: string): bool = - return data.hasKey(key) - , + return data.hasKey(key), ) # Safely saves a Keystore's JsonNode to disk. diff --git a/waku/waku_lightpush/callbacks.nim b/waku/waku_lightpush/callbacks.nim index 93128fd100..1c0396f25d 100644 --- a/waku/waku_lightpush/callbacks.nim +++ b/waku/waku_lightpush/callbacks.nim @@ -5,6 +5,7 @@ import ../waku_relay, ./common, ./protocol, + ./protocol_metrics, ../waku_rln_relay, ../waku_rln_relay/protocol_types @@ -54,8 +55,6 @@ proc getRelayPushHandler*( ## 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", msg_hash = msgHash - return err( - "Lightpush request has not been published to any peers. msg_hash: " & msgHash - ) + return err(protocol_metrics.notPublishedAnyPeer) return ok() diff --git a/waku/waku_lightpush/client.nim b/waku/waku_lightpush/client.nim index da502d4562..4f516dec5d 100644 --- a/waku/waku_lightpush/client.nim +++ b/waku/waku_lightpush/client.nim @@ -3,6 +3,7 @@ import std/options, results, chronicles, chronos, metrics, bearssl/rand import ../node/peer_manager, + ../node/delivery_monitor/publish_observer, ../utils/requests, ../waku_core, ./common, @@ -16,12 +17,16 @@ logScope: type WakuLightPushClient* = ref object peerManager*: PeerManager rng*: ref rand.HmacDrbgContext + publishObservers: seq[PublishObserver] proc new*( T: type WakuLightPushClient, peerManager: PeerManager, rng: ref rand.HmacDrbgContext ): T = WakuLightPushClient(peerManager: peerManager, rng: rng) +proc addPublishObserver*(wl: WakuLightPushClient, obs: PublishObserver) = + wl.publishObservers.add(obs) + proc sendPushRequest( wl: WakuLightPushClient, req: PushRequest, peer: PeerId | RemotePeerInfo ): Future[WakuLightPushResult[void]] {.async, gcsafe.} = @@ -67,4 +72,26 @@ proc publish*( peer: PeerId | RemotePeerInfo, ): Future[WakuLightPushResult[void]] {.async, gcsafe.} = let pushRequest = PushRequest(pubSubTopic: pubSubTopic, message: message) - return await wl.sendPushRequest(pushRequest, peer) + ?await wl.sendPushRequest(pushRequest, peer) + + for obs in wl.publishObservers: + obs.onMessagePublished(pubSubTopic, message) + + return ok() + +proc publishToAny*( + wl: WakuLightPushClient, pubSubTopic: PubsubTopic, message: WakuMessage +): Future[WakuLightPushResult[void]] {.async, gcsafe.} = + ## This proc is similar to the publish one but in this case + ## we don't specify a particular peer and instead we get it from peer manager + + let peer = wl.peerManager.selectPeer(WakuLightPushCodec).valueOr: + return err("could not retrieve a peer supporting WakuLightPushCodec") + + let pushRequest = PushRequest(pubSubTopic: pubSubTopic, message: message) + ?await wl.sendPushRequest(pushRequest, peer) + + for obs in wl.publishObservers: + obs.onMessagePublished(pubSubTopic, message) + + return ok() diff --git a/waku/waku_lightpush/protocol.nim b/waku/waku_lightpush/protocol.nim index b646a1571f..b47f6e7ad9 100644 --- a/waku/waku_lightpush/protocol.nim +++ b/waku/waku_lightpush/protocol.nim @@ -110,4 +110,5 @@ proc new*( requestRateLimiter: newRequestRateLimiter(rateLimitSetting), ) wl.initProtocolHandler() + setServiceLimitMetric(WakuLightpushCodec, rateLimitSetting) return wl diff --git a/waku/waku_lightpush/protocol_metrics.nim b/waku/waku_lightpush/protocol_metrics.nim index 7a30f9e705..ce48a7d3d9 100644 --- a/waku/waku_lightpush/protocol_metrics.nim +++ b/waku/waku_lightpush/protocol_metrics.nim @@ -16,3 +16,4 @@ const emptyResponseBodyFailure* = "empty_response_body_failure" messagePushFailure* = "message_push_failure" requestLimitReachedFailure* = "request_limit_reached_failure" + notPublishedAnyPeer* = "not_published_to_any_peer" diff --git a/waku/waku_metadata/protocol.nim b/waku/waku_metadata/protocol.nim index 40b15a399e..d567dc7845 100644 --- a/waku/waku_metadata/protocol.nim +++ b/waku/waku_metadata/protocol.nim @@ -128,17 +128,17 @@ proc subscriptionsListener(wm: WakuMetadata) {.async.} = let events = await wm.topicSubscriptionQueue.waitEvents(key) for event in events: - let parsedTopic = NsPubsubTopic.parse(event.topic).valueOr: + let parsedShard = RelayShard.parse(event.topic).valueOr: continue - if parsedTopic.clusterId != wm.clusterId: + if parsedShard.clusterId != wm.clusterId: continue case event.kind of PubsubSub: - wm.shards.incl(parsedTopic.shardId) + wm.shards.incl(parsedShard.shardId) of PubsubUnsub: - wm.shards.excl(parsedTopic.shardId) + wm.shards.excl(parsedShard.shardId) else: continue diff --git a/waku/waku_peer_exchange.nim b/waku/waku_peer_exchange.nim index ca707f1621..994989df44 100644 --- a/waku/waku_peer_exchange.nim +++ b/waku/waku_peer_exchange.nim @@ -1,5 +1,5 @@ {.push raises: [].} -import ./waku_peer_exchange/protocol +import ./waku_peer_exchange/[protocol, rpc] -export protocol +export protocol, rpc diff --git a/waku/waku_peer_exchange/protocol.nim b/waku/waku_peer_exchange/protocol.nim index 689c8fddab..0374e12772 100644 --- a/waku/waku_peer_exchange/protocol.nim +++ b/waku/waku_peer_exchange/protocol.nim @@ -13,7 +13,8 @@ import ../waku_core, ../discovery/waku_discv5, ./rpc, - ./rpc_codec + ./rpc_codec, + ../common/rate_limit/request_limiter declarePublicGauge waku_px_peers_received_total, "number of ENRs received via peer exchange" @@ -32,7 +33,7 @@ const DefaultMaxRpcSize* = 10 * DefaultMaxWakuMessageSize + 64 * 1024 # TODO what is the expected size of a PX message? As currently specified, it can contain an arbitary number of ENRs... MaxPeersCacheSize = 60 - CacheRefreshInterval = 15.minutes + CacheRefreshInterval = 10.minutes WakuPeerExchangeCodec* = "/vac/waku/peer-exchange/2.0.0-alpha1" @@ -45,37 +46,55 @@ const pxFailure = "px_failure" type - WakuPeerExchangeResult*[T] = Result[T, string] + WakuPeerExchangeResult*[T] = Result[T, PeerExchangeResponseStatus] WakuPeerExchange* = ref object of LPProtocol peerManager*: PeerManager enrCache*: seq[enr.Record] cluster*: Option[uint16] # todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/ + requestRateLimiter*: RequestRateLimiter proc request*( wpx: WakuPeerExchange, numPeers: uint64, conn: Connection ): Future[WakuPeerExchangeResult[PeerExchangeResponse]] {.async: (raises: []).} = - let rpc = PeerExchangeRpc(request: PeerExchangeRequest(numPeers: numPeers)) + let rpc = PeerExchangeRpc.makeRequest(numPeers) var buffer: seq[byte] - var error: string + var callResult = + (status_code: PeerExchangeResponseStatusCode.SUCCESS, status_desc: none(string)) try: await conn.writeLP(rpc.encode().buffer) buffer = await conn.readLp(DefaultMaxRpcSize.int) except CatchableError as exc: waku_px_errors.inc(labelValues = [exc.msg]) - error = $exc.msg + callResult = ( + status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE, + status_desc: some($exc.msg), + ) finally: # close, no more data is expected await conn.closeWithEof() - if error.len > 0: - return err("write/read failed: " & error) + if callResult.status_code != PeerExchangeResponseStatusCode.SUCCESS: + return err(callResult) let decodedBuff = PeerExchangeRpc.decode(buffer) if decodedBuff.isErr(): - return err("decode failed: " & $decodedBuff.error) + return err( + ( + status_code: PeerExchangeResponseStatusCode.BAD_RESPONSE, + status_desc: some($decodedBuff.error), + ) + ) + if decodedBuff.get().response.status_code != PeerExchangeResponseStatusCode.SUCCESS: + return err( + ( + status_code: decodedBuff.get().response.status_code, + status_desc: decodedBuff.get().response.status_desc, + ) + ) + return ok(decodedBuff.get().response) proc request*( @@ -84,10 +103,20 @@ proc request*( try: let connOpt = await wpx.peerManager.dialPeer(peer, WakuPeerExchangeCodec) if connOpt.isNone(): - return err(dialFailure) + return err( + ( + status_code: PeerExchangeResponseStatusCode.DIAL_FAILURE, + status_desc: some(dialFailure), + ) + ) return await wpx.request(numPeers, connOpt.get()) except CatchableError: - return err("exception dialing peer: " & getCurrentExceptionMsg()) + return err( + ( + status_code: PeerExchangeResponseStatusCode.BAD_RESPONSE, + status_desc: some("exception dialing peer: " & getCurrentExceptionMsg()), + ) + ) proc request*( wpx: WakuPeerExchange, numPeers: uint64 @@ -95,22 +124,50 @@ proc request*( let peerOpt = wpx.peerManager.selectPeer(WakuPeerExchangeCodec) if peerOpt.isNone(): waku_px_errors.inc(labelValues = [peerNotFoundFailure]) - return err(peerNotFoundFailure) + return err( + ( + status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE, + status_desc: some(peerNotFoundFailure), + ) + ) return await wpx.request(numPeers, peerOpt.get()) proc respond( wpx: WakuPeerExchange, enrs: seq[enr.Record], conn: Connection ): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} = - let rpc = PeerExchangeRpc( - response: - PeerExchangeResponse(peerInfos: enrs.mapIt(PeerExchangePeerInfo(enr: it.raw))) - ) + let rpc = PeerExchangeRpc.makeResponse(enrs.mapIt(PeerExchangePeerInfo(enr: it.raw))) try: await conn.writeLP(rpc.encode().buffer) except CatchableError as exc: waku_px_errors.inc(labelValues = [exc.msg]) - return err(exc.msg) + return err( + ( + status_code: PeerExchangeResponseStatusCode.DIAL_FAILURE, + status_desc: some("exception dialing peer: " & exc.msg), + ) + ) + + return ok() + +proc respondError( + wpx: WakuPeerExchange, + status_code: PeerExchangeResponseStatusCode, + status_desc: Option[string], + conn: Connection, +): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} = + let rpc = PeerExchangeRpc.makeErrorResponse(status_code, status_desc) + + try: + await conn.writeLP(rpc.encode().buffer) + except CatchableError as exc: + waku_px_errors.inc(labelValues = [exc.msg]) + return err( + ( + status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE, + status_desc: some("exception dialing peer: " & exc.msg), + ) + ) return ok() @@ -169,26 +226,44 @@ proc updatePxEnrCache(wpx: WakuPeerExchange) {.async.} = proc initProtocolHandler(wpx: WakuPeerExchange) = proc handler(conn: Connection, proto: string) {.async, gcsafe, closure.} = var buffer: seq[byte] - try: - buffer = await conn.readLp(DefaultMaxRpcSize.int) - except CatchableError as exc: - waku_px_errors.inc(labelValues = [exc.msg]) - return - - let decBuf = PeerExchangeRpc.decode(buffer) - if decBuf.isErr(): - waku_px_errors.inc(labelValues = [decodeRpcFailure]) - return - - let rpc = decBuf.get() - trace "peer exchange request received" - let enrs = wpx.getEnrsFromCache(rpc.request.numPeers) - let res = await wpx.respond(enrs, conn) - if res.isErr: - waku_px_errors.inc(labelValues = [res.error]) - else: - waku_px_peers_sent.inc(enrs.len().int64()) - + wpx.requestRateLimiter.checkUsageLimit(WakuPeerExchangeCodec, conn): + try: + buffer = await conn.readLp(DefaultMaxRpcSize.int) + except CatchableError as exc: + waku_px_errors.inc(labelValues = [exc.msg]) + + ( + await wpx.respondError( + PeerExchangeResponseStatusCode.BAD_REQUEST, some(exc.msg), conn + ) + ).isOkOr: + error "Failed to respond with BAD_REQUEST:", error = $error + return + + let decBuf = PeerExchangeRpc.decode(buffer) + if decBuf.isErr(): + waku_px_errors.inc(labelValues = [decodeRpcFailure]) + error "Failed to decode PeerExchange request", error = $decBuf.error + + ( + await wpx.respondError( + PeerExchangeResponseStatusCode.BAD_REQUEST, some($decBuf.error), conn + ) + ).isOkOr: + error "Failed to respond with BAD_REQUEST:", error = $error + return + + trace "peer exchange request received" + let enrs = wpx.getEnrsFromCache(decBuf.get().request.numPeers) + (await wpx.respond(enrs, conn)).isErrOr: + waku_px_peers_sent.inc(enrs.len().int64()) + do: + ( + await wpx.respondError( + PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS, none(string), conn + ) + ).isOkOr: + error "Failed to respond with TOO_MANY_REQUESTS:", error = $error # close, no data is expected await conn.closeWithEof() @@ -199,8 +274,14 @@ proc new*( T: type WakuPeerExchange, peerManager: PeerManager, cluster: Option[uint16] = none(uint16), + rateLimitSetting: Option[RateLimitSetting] = none[RateLimitSetting](), ): T = - let wpx = WakuPeerExchange(peerManager: peerManager, cluster: cluster) + let wpx = WakuPeerExchange( + peerManager: peerManager, + cluster: cluster, + requestRateLimiter: newRequestRateLimiter(rateLimitSetting), + ) wpx.initProtocolHandler() + setServiceLimitMetric(WakuPeerExchangeCodec, rateLimitSetting) asyncSpawn wpx.updatePxEnrCache() return wpx diff --git a/waku/waku_peer_exchange/rpc.nim b/waku/waku_peer_exchange/rpc.nim index 0b248d9356..4a91e8db1e 100644 --- a/waku/waku_peer_exchange/rpc.nim +++ b/waku/waku_peer_exchange/rpc.nim @@ -1,4 +1,15 @@ +import std/options + type + PeerExchangeResponseStatusCode* {.pure.} = enum + UNKNOWN = uint32(000) + SUCCESS = uint32(200) + BAD_REQUEST = uint32(400) + BAD_RESPONSE = uint32(401) + TOO_MANY_REQUESTS = uint32(429) + SERVICE_UNAVAILABLE = uint32(503) + DIAL_FAILURE = uint32(599) + PeerExchangePeerInfo* = object enr*: seq[byte] # RLP encoded ENR: https://eips.ethereum.org/EIPS/eip-778 @@ -7,7 +18,44 @@ type PeerExchangeResponse* = object peerInfos*: seq[PeerExchangePeerInfo] + status_code*: PeerExchangeResponseStatusCode + status_desc*: Option[string] + + PeerExchangeResponseStatus* = + tuple[status_code: PeerExchangeResponseStatusCode, status_desc: Option[string]] PeerExchangeRpc* = object request*: PeerExchangeRequest response*: PeerExchangeResponse + +proc makeRequest*(T: type PeerExchangeRpc, numPeers: uint64): T = + return T(request: PeerExchangeRequest(numPeers: numPeers)) + +proc makeResponse*(T: type PeerExchangeRpc, peerInfos: seq[PeerExchangePeerInfo]): T = + return T( + response: PeerExchangeResponse( + peerInfos: peerInfos, status_code: PeerExchangeResponseStatusCode.SUCCESS + ) + ) + +proc makeErrorResponse*( + T: type PeerExchangeRpc, + status_code: PeerExchangeResponseStatusCode, + status_desc: Option[string] = none(string), +): T = + return T( + response: PeerExchangeResponse(status_code: status_code, status_desc: status_desc) + ) + +proc `$`*(statusCode: PeerExchangeResponseStatusCode): string = + case statusCode + of PeerExchangeResponseStatusCode.UNKNOWN: "UNKNOWN" + of PeerExchangeResponseStatusCode.SUCCESS: "SUCCESS" + of PeerExchangeResponseStatusCode.BAD_REQUEST: "BAD_REQUEST" + of PeerExchangeResponseStatusCode.BAD_RESPONSE: "BAD_RESPONSE" + of PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS" + of PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE" + of PeerExchangeResponseStatusCode.DIAL_FAILURE: "DIAL_FAILURE" + +# proc `$`*(pxResponseStatus: PeerExchangeResponseStatus): string = +# return $pxResponseStatus.status & " - " & pxResponseStatus.desc.get("") diff --git a/waku/waku_peer_exchange/rpc_codec.nim b/waku/waku_peer_exchange/rpc_codec.nim index 92ebd70842..e5e982938f 100644 --- a/waku/waku_peer_exchange/rpc_codec.nim +++ b/waku/waku_peer_exchange/rpc_codec.nim @@ -1,5 +1,6 @@ {.push raises: [].} +import std/options import ../common/protobuf, ./rpc proc encode*(rpc: PeerExchangeRequest): ProtoBuffer = @@ -38,17 +39,26 @@ proc decode*(T: type PeerExchangePeerInfo, buffer: seq[byte]): ProtoResult[T] = ok(rpc) +proc parse*(T: type PeerExchangeResponseStatusCode, status: uint32): T = + case status + of 200, 400, 429, 503: + PeerExchangeResponseStatusCode(status) + else: + PeerExchangeResponseStatusCode.UNKNOWN + proc encode*(rpc: PeerExchangeResponse): ProtoBuffer = var pb = initProtoBuffer() for pi in rpc.peerInfos: pb.write3(1, pi.encode()) + pb.write3(10, rpc.status_code.uint32) + pb.write3(11, rpc.status_desc) pb.finish3() pb -proc decode*(T: type PeerExchangeResponse, buffer: seq[byte]): ProtoResult[T] = +proc decode*(T: type PeerExchangeResponse, buffer: seq[byte]): ProtobufResult[T] = let pb = initProtoBuffer(buffer) var rpc = PeerExchangeResponse(peerInfos: @[]) @@ -57,6 +67,18 @@ proc decode*(T: type PeerExchangeResponse, buffer: seq[byte]): ProtoResult[T] = for pib in peerInfoBuffers: rpc.peerInfos.add(?PeerExchangePeerInfo.decode(pib)) + var status_code: uint32 + if ?pb.getField(10, status_code): + rpc.status_code = PeerExchangeResponseStatusCode.parse(status_code) + else: + return err(ProtobufError.missingRequiredField("status_code")) + + var status_desc: string + if ?pb.getField(11, status_desc): + rpc.status_desc = some(status_desc) + else: + rpc.status_desc = none(string) + ok(rpc) proc encode*(rpc: PeerExchangeRpc): ProtoBuffer = @@ -64,21 +86,25 @@ proc encode*(rpc: PeerExchangeRpc): ProtoBuffer = pb.write3(1, rpc.request.encode()) pb.write3(2, rpc.response.encode()) + pb.finish3() pb -proc decode*(T: type PeerExchangeRpc, buffer: seq[byte]): ProtoResult[T] = +proc decode*(T: type PeerExchangeRpc, buffer: seq[byte]): ProtobufResult[T] = let pb = initProtoBuffer(buffer) var rpc = PeerExchangeRpc() var requestBuffer: seq[byte] if not ?pb.getField(1, requestBuffer): - return err(ProtoError.RequiredFieldMissing) + return err(ProtobufError.missingRequiredField("request")) + rpc.request = ?PeerExchangeRequest.decode(requestBuffer) var responseBuffer: seq[byte] - discard ?pb.getField(2, responseBuffer) + if not ?pb.getField(2, responseBuffer): + return err(ProtobufError.missingRequiredField("response")) + rpc.response = ?PeerExchangeResponse.decode(responseBuffer) ok(rpc) diff --git a/waku/waku_relay/protocol.nim b/waku/waku_relay/protocol.nim index b922f69d3b..fc8aa41725 100644 --- a/waku/waku_relay/protocol.nim +++ b/waku/waku_relay/protocol.nim @@ -18,7 +18,7 @@ import libp2p/protocols/pubsub/rpc/messages, libp2p/stream/connection, libp2p/switch -import ../waku_core, ./message_id +import ../waku_core, ./message_id, ../node/delivery_monitor/publish_observer logScope: topics = "waku relay" @@ -58,7 +58,8 @@ const TopicParameters = TopicParams( ) declareCounter waku_relay_network_bytes, - "total traffic per topic", labels = ["topic", "direction"] + "total traffic per topic, distinct gross/net and direction", + labels = ["topic", "type", "direction"] # see: https://rfc.vac.dev/spec/29/#gossipsub-v10-parameters const GossipsubParameters = GossipSubParams.init( @@ -128,6 +129,7 @@ type wakuValidators: seq[tuple[handler: WakuValidatorHandler, errorMessage: string]] # a map of validators to error messages to return when validation fails validatorInserted: Table[PubsubTopic, bool] + publishObservers: seq[PublishObserver] proc initProtocolHandler(w: WakuRelay) = proc handler(conn: Connection, proto: string) {.async.} = @@ -151,7 +153,36 @@ proc initProtocolHandler(w: WakuRelay) = w.handler = handler w.codec = WakuRelayCodec -proc initRelayMetricObserver(w: WakuRelay) = +proc logMessageInfo*( + w: WakuRelay, + remotePeerId: string, + topic: string, + msg_id_short: string, + msg: WakuMessage, + onRecv: bool, +) = + let msg_hash = computeMessageHash(topic, msg).to0xHex() + + if onRecv: + notice "received relay message", + my_peer_id = w.switch.peerInfo.peerId, + msg_hash = msg_hash, + msg_id = msg_id_short, + from_peer_id = remotePeerId, + topic = topic, + receivedTime = getNowInNanosecondTime(), + payloadSizeBytes = msg.payload.len + else: + notice "sent relay message", + my_peer_id = w.switch.peerInfo.peerId, + msg_hash = msg_hash, + msg_id = msg_id_short, + to_peer_id = remotePeerId, + topic = topic, + sentTime = getNowInNanosecondTime(), + payloadSizeBytes = msg.payload.len + +proc initRelayObservers(w: WakuRelay) = proc decodeRpcMessageInfo( peer: PubSubPeer, msg: Message ): Result[ @@ -179,20 +210,6 @@ proc initRelayMetricObserver(w: WakuRelay) = let msgSize = msg.data.len + msg.topic.len return ok((msg_id_short, msg.topic, wakuMessage, msgSize)) - proc logMessageInfo( - peer: PubSubPeer, topic: string, msg_id_short: string, msg: WakuMessage - ) = - let msg_hash = computeMessageHash(topic, msg).to0xHex() - - notice "sent relay message", - my_peer_id = w.switch.peerInfo.peerId, - msg_hash = msg_hash, - msg_id = msg_id_short, - to_peer_id = peer.peerId, - topic = topic, - sentTime = getNowInNanosecondTime(), - payloadSizeBytes = msg.payload.len - proc updateMetrics( peer: PubSubPeer, pubsub_topic: string, @@ -200,26 +217,53 @@ proc initRelayMetricObserver(w: WakuRelay) = msgSize: int, onRecv: bool, ) = - waku_relay_network_bytes.inc( - msgSize.int64, labelValues = [pubsub_topic, if onRecv: "in" else: "out"] - ) + if onRecv: + waku_relay_network_bytes.inc( + msgSize.int64, labelValues = [pubsub_topic, "gross", "in"] + ) + else: + # sent traffic can only be "net" + # TODO: If we can measure unsuccessful sends would mean a possible distinction between gross/net + waku_relay_network_bytes.inc( + msgSize.int64, labelValues = [pubsub_topic, "net", "out"] + ) proc onRecv(peer: PubSubPeer, msgs: var RPCMsg) = for msg in msgs.messages: let (msg_id_short, topic, wakuMessage, msgSize) = decodeRpcMessageInfo(peer, msg).valueOr: continue - # message receive log happens in treaceHandler as this one is called before checks + # message receive log happens in onValidated observer as onRecv is called before checks updateMetrics(peer, topic, wakuMessage, msgSize, onRecv = true) discard + proc onValidated(peer: PubSubPeer, msg: Message, msgId: MessageId) = + let msg_id_short = shortLog(msgId) + let wakuMessage = WakuMessage.decode(msg.data).valueOr: + warn "onValidated: failed decoding to Waku Message", + my_peer_id = w.switch.peerInfo.peerId, + msg_id = msg_id_short, + from_peer_id = peer.peerId, + pubsub_topic = msg.topic, + error = $error + return + + logMessageInfo( + w, shortLog(peer.peerId), msg.topic, msg_id_short, wakuMessage, onRecv = true + ) + proc onSend(peer: PubSubPeer, msgs: var RPCMsg) = for msg in msgs.messages: let (msg_id_short, topic, wakuMessage, msgSize) = decodeRpcMessageInfo(peer, msg).valueOr: + warn "onSend: failed decoding RPC info", + my_peer_id = w.switch.peerInfo.peerId, to_peer_id = peer.peerId continue - logMessageInfo(peer, topic, msg_id_short, wakuMessage) + logMessageInfo( + w, shortLog(peer.peerId), topic, msg_id_short, wakuMessage, onRecv = false + ) updateMetrics(peer, topic, wakuMessage, msgSize, onRecv = false) - let administrativeObserver = PubSubObserver(onRecv: onRecv, onSend: onSend) + let administrativeObserver = + PubSubObserver(onRecv: onRecv, onSend: onSend, onValidated: onValidated) w.addObserver(administrativeObserver) @@ -243,7 +287,7 @@ proc new*( procCall GossipSub(w).initPubSub() w.initProtocolHandler() - w.initRelayMetricObserver() + w.initRelayObservers() except InitializationError: return err("initialization error: " & getCurrentExceptionMsg()) @@ -254,7 +298,14 @@ proc addValidator*( ) {.gcsafe.} = w.wakuValidators.add((handler, errorMessage)) +proc addPublishObserver*(w: WakuRelay, obs: PublishObserver) = + ## Observer when the api client performed a publish operation. This + ## is initially aimed for bringing an additional layer of delivery reliability thanks + ## to store + w.publishObservers.add(obs) + proc addObserver*(w: WakuRelay, observer: PubSubObserver) {.gcsafe.} = + ## Observes when a message is sent/received from the GossipSub PoV procCall GossipSub(w).addObserver(observer) method start*(w: WakuRelay) {.async, base.} = @@ -349,6 +400,12 @@ proc subscribe*( fut.complete() return fut else: + # this subscription handler is called once for every validated message + # that will be relayed, hence this is the place we can count net incoming traffic + waku_relay_network_bytes.inc( + data.len.int64 + pubsubTopic.len.int64, labelValues = [pubsubTopic, "net", "in"] + ) + return handler(pubsubTopic, decMsg.get()) # Add the ordered validator to the topic @@ -391,4 +448,56 @@ proc publish*( let relayedPeerCount = await procCall GossipSub(w).publish(pubsubTopic, data) + if relayedPeerCount > 0: + for obs in w.publishObservers: + obs.onMessagePublished(pubSubTopic, message) + return relayedPeerCount + +proc getNumPeersInMesh*(w: WakuRelay, pubsubTopic: PubsubTopic): Result[int, string] = + ## Returns the number of peers in a mesh defined by the passed pubsub topic. + ## The 'mesh' atribute is defined in the GossipSub ref object. + + if not w.mesh.hasKey(pubsubTopic): + return err( + "getNumPeersInMesh - there is no mesh peer for the given pubsub topic: " & + pubsubTopic + ) + + let peersRes = catch: + w.mesh[pubsubTopic] + + let peers: HashSet[PubSubPeer] = peersRes.valueOr: + return + err("getNumPeersInMesh - exception accessing " & pubsubTopic & ": " & error.msg) + + return ok(peers.len) + +proc getNumConnectedPeers*( + w: WakuRelay, pubsubTopic: PubsubTopic +): Result[int, string] = + ## Returns the number of connected peers and subscribed to the passed pubsub topic. + ## The 'gossipsub' atribute is defined in the GossipSub ref object. + + if pubsubTopic == "": + ## Return all the connected peers + var numConnPeers = 0 + for k, v in w.gossipsub: + numConnPeers.inc(v.len) + return ok(numConnPeers) + + if not w.gossipsub.hasKey(pubsubTopic): + return err( + "getNumConnectedPeers - there is no gossipsub peer for the given pubsub topic: " & + pubsubTopic + ) + + let peersRes = catch: + w.gossipsub[pubsubTopic] + + let peers: HashSet[PubSubPeer] = peersRes.valueOr: + return err( + "getNumConnectedPeers - exception accessing " & pubsubTopic & ": " & error.msg + ) + + return ok(peers.len) diff --git a/waku/waku_rln_relay/protocol_metrics.nim b/waku/waku_rln_relay/protocol_metrics.nim index 6afb448f08..5d1ea563c4 100644 --- a/waku/waku_rln_relay/protocol_metrics.nim +++ b/waku/waku_rln_relay/protocol_metrics.nim @@ -80,24 +80,26 @@ proc getRlnMetricsLogger*(): RLNMetricsLogger = var cumulativeValidMessages = 0.float64 var cumulativeProofs = 0.float64 - logMetrics = proc() = - {.gcsafe.}: - let freshErrorCount = parseAndAccumulate(waku_rln_errors_total, cumulativeErrors) - let freshMsgCount = - parseAndAccumulate(waku_rln_messages_total, cumulativeMessages) - let freshSpamCount = - parseAndAccumulate(waku_rln_spam_messages_total, cumulativeSpamMessages) - let freshInvalidMsgCount = - parseAndAccumulate(waku_rln_invalid_messages_total, cumulativeInvalidMessages) - let freshValidMsgCount = - parseAndAccumulate(waku_rln_valid_messages_total, cumulativeValidMessages) - let freshProofCount = - parseAndAccumulate(waku_rln_proof_verification_total, cumulativeProofs) + when defined(metrics): + logMetrics = proc() = + {.gcsafe.}: + let freshErrorCount = + parseAndAccumulate(waku_rln_errors_total, cumulativeErrors) + let freshMsgCount = + parseAndAccumulate(waku_rln_messages_total, cumulativeMessages) + let freshSpamCount = + parseAndAccumulate(waku_rln_spam_messages_total, cumulativeSpamMessages) + let freshInvalidMsgCount = + parseAndAccumulate(waku_rln_invalid_messages_total, cumulativeInvalidMessages) + let freshValidMsgCount = + parseAndAccumulate(waku_rln_valid_messages_total, cumulativeValidMessages) + let freshProofCount = + parseAndAccumulate(waku_rln_proof_verification_total, cumulativeProofs) - info "Total messages", count = freshMsgCount - info "Total spam messages", count = freshSpamCount - info "Total invalid messages", count = freshInvalidMsgCount - info "Total valid messages", count = freshValidMsgCount - info "Total errors", count = freshErrorCount - info "Total proofs verified", count = freshProofCount + info "Total messages", count = freshMsgCount + info "Total spam messages", count = freshSpamCount + info "Total invalid messages", count = freshInvalidMsgCount + info "Total valid messages", count = freshValidMsgCount + info "Total errors", count = freshErrorCount + info "Total proofs verified", count = freshProofCount return logMetrics diff --git a/waku/waku_store/client.nim b/waku/waku_store/client.nim index aa3f6629dd..61229576ab 100644 --- a/waku/waku_store/client.nim +++ b/waku/waku_store/client.nim @@ -24,7 +24,8 @@ proc sendStoreRequest( ): Future[StoreQueryResult] {.async, gcsafe.} = var req = request - req.requestId = generateRequestId(self.rng) + if req.requestId == "": + req.requestId = generateRequestId(self.rng) let writeRes = catch: await connection.writeLP(req.encode().buffer) @@ -48,7 +49,7 @@ proc sendStoreRequest( return ok(res) proc query*( - self: WakuStoreClient, request: StoreQueryRequest, peer: RemotePeerInfo + self: WakuStoreClient, request: StoreQueryRequest, peer: RemotePeerInfo | PeerId ): Future[StoreQueryResult] {.async, gcsafe.} = if request.paginationCursor.isSome() and request.paginationCursor.get() == EmptyCursor: return err(StoreError(kind: ErrorCode.BAD_REQUEST, cause: "invalid cursor")) @@ -59,3 +60,22 @@ proc query*( return err(StoreError(kind: ErrorCode.PEER_DIAL_FAILURE, address: $peer)) return await self.sendStoreRequest(request, connection) + +proc queryToAny*( + self: WakuStoreClient, request: StoreQueryRequest, peerId = none(PeerId) +): Future[StoreQueryResult] {.async.} = + ## This proc is similar to the query one but in this case + ## we don't specify a particular peer and instead we get it from peer manager + + if request.paginationCursor.isSome() and request.paginationCursor.get() == EmptyCursor: + return err(StoreError(kind: ErrorCode.BAD_REQUEST, cause: "invalid cursor")) + + let peer = self.peerManager.selectPeer(WakuStoreCodec).valueOr: + return err(StoreError(kind: BAD_RESPONSE, cause: "no service store peer connected")) + + let connection = (await self.peerManager.dialPeer(peer, WakuStoreCodec)).valueOr: + waku_store_errors.inc(labelValues = [dialFailure]) + + return err(StoreError(kind: ErrorCode.PEER_DIAL_FAILURE, address: $peer)) + + return await self.sendStoreRequest(request, connection) diff --git a/waku/waku_store/protocol.nim b/waku/waku_store/protocol.nim index a4e5467a23..5a8a81c132 100644 --- a/waku/waku_store/protocol.nim +++ b/waku/waku_store/protocol.nim @@ -4,7 +4,7 @@ {.push raises: [].} import - std/options, + std/[options, times], results, chronicles, chronos, @@ -25,9 +25,6 @@ import logScope: topics = "waku store" -const MaxMessageTimestampVariance* = getNanoSecondTime(20) - # 20 seconds maximum allowable sender timestamp "drift" - type StoreQueryRequestHandler* = proc(req: StoreQueryRequest): Future[StoreQueryResult] {.async, gcsafe.} @@ -39,9 +36,11 @@ type WakuStore* = ref object of LPProtocol ## Protocol +type StoreResp = tuple[resp: seq[byte], requestId: string] + proc handleQueryRequest( self: WakuStore, requestor: PeerId, raw_request: seq[byte] -): Future[seq[byte]] {.async.} = +): Future[StoreResp] {.async.} = var res = StoreQueryResponse() let req = StoreQueryRequest.decode(raw_request).valueOr: @@ -51,7 +50,7 @@ proc handleQueryRequest( res.statusCode = uint32(ErrorCode.BAD_REQUEST) res.statusDesc = "decoding rpc failed: " & $error - return res.encode().buffer + return (res.encode().buffer, "not_parsed_requestId") let requestId = req.requestId @@ -68,7 +67,7 @@ proc handleQueryRequest( res.statusCode = uint32(error.kind) res.statusDesc = $error - return res.encode().buffer + return (res.encode().buffer, "not_parsed_requestId") res.requestId = requestId res.statusCode = 200 @@ -77,7 +76,7 @@ proc handleQueryRequest( info "sending store query response", peerId = requestor, requestId = requestId, messages = res.messages.len - return res.encode().buffer + return (res.encode().buffer, requestId) proc initProtocolHandler(self: WakuStore) = let rejectReposnseBuffer = StoreQueryResponse( @@ -90,7 +89,8 @@ proc initProtocolHandler(self: WakuStore) = ).encode().buffer proc handler(conn: Connection, proto: string) {.async, gcsafe, closure.} = - var resBuf: seq[byte] + var successfulQuery = false ## only consider the correct queries in metrics + var resBuf: StoreResp self.requestRateLimiter.checkUsageLimit(WakuStoreCodec, conn): let readRes = catch: await conn.readLp(DefaultMaxRpcSize.int) @@ -103,21 +103,34 @@ proc initProtocolHandler(self: WakuStore) = amount = reqBuf.len().int64, labelValues = [WakuStoreCodec, "in"] ) + let queryStartTime = getTime().toUnixFloat() + resBuf = await self.handleQueryRequest(conn.peerId, reqBuf) + + let queryDuration = getTime().toUnixFloat() - queryStartTime + waku_store_time_seconds.set(queryDuration, ["query-db-time"]) + successfulQuery = true do: debug "store query request rejected due rate limit exceeded", peerId = conn.peerId, limit = $self.requestRateLimiter.setting - resBuf = rejectReposnseBuffer + resBuf = (rejectReposnseBuffer, "rejected") + + let writeRespStartTime = getTime().toUnixFloat() let writeRes = catch: - await conn.writeLp(resBuf) + await conn.writeLp(resBuf.resp) if writeRes.isErr(): error "Connection write error", error = writeRes.error.msg return + debug "after sending response", requestId = resBuf.requestId + if successfulQuery: + let writeDuration = getTime().toUnixFloat() - writeRespStartTime + waku_store_time_seconds.set(writeDuration, ["send-store-resp-time"]) + waku_service_network_bytes.inc( - amount = resBuf.len().int64, labelValues = [WakuStoreCodec, "out"] + amount = resBuf.resp.len().int64, labelValues = [WakuStoreCodec, "out"] ) self.handler = handler @@ -141,5 +154,5 @@ proc new*( ) store.initProtocolHandler() - + setServiceLimitMetric(WakuStoreCodec, rateLimitSetting) return store diff --git a/waku/waku_store/protocol_metrics.nim b/waku/waku_store/protocol_metrics.nim index d413c0a678..b077147a67 100644 --- a/waku/waku_store/protocol_metrics.nim +++ b/waku/waku_store/protocol_metrics.nim @@ -5,6 +5,11 @@ import metrics declarePublicGauge waku_store_errors, "number of store protocol errors", ["type"] declarePublicGauge waku_store_queries, "number of store queries received" +## "query-db-time" phase considers the time when node performs the query to the database. +## "send-store-resp-time" phase is the time when node writes the store response to the store-client. +declarePublicGauge waku_store_time_seconds, + "Time in seconds spent by each store phase", labels = ["phase"] + # Error types (metric label values) const dialFailure* = "dial_failure" diff --git a/waku/waku_store_legacy/client.nim b/waku/waku_store_legacy/client.nim index 2e87c4ca48..f26906e9e6 100644 --- a/waku/waku_store_legacy/client.nim +++ b/waku/waku_store_legacy/client.nim @@ -43,7 +43,13 @@ proc sendHistoryQueryRPC( let connection = connOpt.get() - let reqRpc = HistoryRPC(requestId: generateRequestId(w.rng), query: some(req.toRPC())) + let requestId = + if req.requestId != "": + req.requestId + else: + generateRequestId(w.rng) + + let reqRpc = HistoryRPC(requestId: requestId, query: some(req.toRPC())) await connection.writeLP(reqRpc.encode().buffer) #TODO: I see a challenge here, if storeNode uses a different MaxRPCSize this read will fail. diff --git a/waku/waku_store_legacy/common.nim b/waku/waku_store_legacy/common.nim index 3f88f9509c..ddbe676d51 100644 --- a/waku/waku_store_legacy/common.nim +++ b/waku/waku_store_legacy/common.nim @@ -45,6 +45,7 @@ type endTime*: Option[Timestamp] pageSize*: uint64 direction*: PagingDirection + requestId*: string HistoryResponse* = object messages*: seq[WakuMessage] diff --git a/waku/waku_store_legacy/protocol.nim b/waku/waku_store_legacy/protocol.nim index 6f158394e2..4e06419b49 100644 --- a/waku/waku_store_legacy/protocol.nim +++ b/waku/waku_store_legacy/protocol.nim @@ -4,7 +4,7 @@ {.push raises: [].} import - std/options, + std/[options, times], results, chronicles, chronos, @@ -26,9 +26,6 @@ import logScope: topics = "waku legacy store" -const MaxMessageTimestampVariance* = getNanoSecondTime(20) - # 20 seconds maximum allowable sender timestamp "drift" - type HistoryQueryHandler* = proc(req: HistoryQuery): Future[HistoryResult] {.async, gcsafe.} @@ -58,9 +55,9 @@ proc handleLegacyQueryRequest( # TODO: Return (BAD_REQUEST, cause: "empty query") return - let - requestId = reqRpc.requestId - request = reqRpc.query.get().toAPI() + let requestId = reqRpc.requestId + var request = reqRpc.query.get().toAPI() + request.requestId = requestId info "received history query", peerId = requestor, requestId = requestId, query = request @@ -105,6 +102,7 @@ proc initProtocolHandler(ws: WakuStore) = ).encode().buffer proc handler(conn: Connection, proto: string) {.async, closure.} = + var successfulQuery = false ## only consider the correct queries in metrics var resBuf: seq[byte] ws.requestRateLimiter.checkUsageLimit(WakuLegacyStoreCodec, conn): let readRes = catch: @@ -118,12 +116,17 @@ proc initProtocolHandler(ws: WakuStore) = amount = reqBuf.len().int64, labelValues = [WakuLegacyStoreCodec, "in"] ) + let queryStartTime = getTime().toUnixFloat() resBuf = await ws.handleLegacyQueryRequest(conn.peerId, reqBuf) + let queryDuration = getTime().toUnixFloat() - queryStartTime + waku_legacy_store_time_seconds.set(queryDuration, ["query-db-time"]) + successfulQuery = true do: debug "Legacy store query request rejected due rate limit exceeded", peerId = conn.peerId, limit = $ws.requestRateLimiter.setting resBuf = rejectResponseBuf + let writeRespStartTime = getTime().toUnixFloat() let writeRes = catch: await conn.writeLp(resBuf) @@ -131,6 +134,10 @@ proc initProtocolHandler(ws: WakuStore) = error "Connection write error", error = writeRes.error.msg return + if successfulQuery: + let writeDuration = getTime().toUnixFloat() - writeRespStartTime + waku_legacy_store_time_seconds.set(writeDuration, ["send-store-resp-time"]) + waku_service_network_bytes.inc( amount = resBuf.len().int64, labelValues = [WakuLegacyStoreCodec, "out"] ) @@ -156,4 +163,5 @@ proc new*( requestRateLimiter: newRequestRateLimiter(rateLimitSetting), ) ws.initProtocolHandler() + setServiceLimitMetric(WakuLegacyStoreCodec, rateLimitSetting) ws diff --git a/waku/waku_store_legacy/protocol_metrics.nim b/waku/waku_store_legacy/protocol_metrics.nim index 59c7a829a5..53cc71427f 100644 --- a/waku/waku_store_legacy/protocol_metrics.nim +++ b/waku/waku_store_legacy/protocol_metrics.nim @@ -6,6 +6,11 @@ declarePublicGauge waku_legacy_store_errors, "number of legacy store protocol errors", ["type"] declarePublicGauge waku_legacy_store_queries, "number of legacy store queries received" +## "query-db-time" phase considers the time when node performs the query to the database. +## "send-store-resp-time" phase is the time when node writes the store response to the store-client. +declarePublicGauge waku_legacy_store_time_seconds, + "Time in seconds spent by each store phase", labels = ["phase"] + # Error types (metric label values) const dialFailure* = "dial_failure" diff --git a/waku/waku_sync.nim b/waku/waku_sync.nim new file mode 100644 index 0000000000..61cb2df4e4 --- /dev/null +++ b/waku/waku_sync.nim @@ -0,0 +1,5 @@ +{.push raises: [].} + +import ./waku_sync/protocol, ./waku_sync/common + +export common, protocol diff --git a/waku/waku_sync/codec.nim b/waku/waku_sync/codec.nim new file mode 100644 index 0000000000..6e3d9bd63b --- /dev/null +++ b/waku/waku_sync/codec.nim @@ -0,0 +1,57 @@ +{.push raises: [].} + +import std/options, stew/arrayops +import ../common/protobuf, ../waku_core, ./common + +proc encode*(req: SyncPayload): ProtoBuffer = + var pb = initProtoBuffer() + + if req.syncRange.isSome(): + pb.write3(31, req.syncRange.get()[0]) + pb.write3(32, req.syncRange.get()[1]) + + if req.frameSize.isSome(): + pb.write3(33, req.frameSize.get()) + + if req.negentropy.len > 0: + pb.write3(1, req.negentropy) + + if req.hashes.len > 0: + for hash in req.hashes: + pb.write3(20, hash) + + return pb + +proc decode*(T: type SyncPayload, buffer: seq[byte]): ProtobufResult[T] = + var req = SyncPayload() + let pb = initProtoBuffer(buffer) + + var rangeStart: uint64 + var rangeEnd: uint64 + if ?pb.getField(31, rangeStart) and ?pb.getField(32, rangeEnd): + req.syncRange = some((rangeStart, rangeEnd)) + else: + req.syncRange = none((uint64, uint64)) + + var frame: uint64 + if ?pb.getField(33, frame): + req.frameSize = some(frame) + else: + req.frameSize = none(uint64) + + var negentropy: seq[byte] + if ?pb.getField(1, negentropy): + req.negentropy = negentropy + else: + req.negentropy = @[] + + var buffer: seq[seq[byte]] + if not ?pb.getRepeatedField(20, buffer): + req.hashes = @[] + else: + req.hashes = newSeqOfCap[WakuMessageHash](buffer.len) + for buf in buffer: + let msg: WakuMessageHash = fromBytes(buf) + req.hashes.add(msg) + + return ok(req) diff --git a/waku/waku_sync/common.nim b/waku/waku_sync/common.nim new file mode 100644 index 0000000000..4796ebf79d --- /dev/null +++ b/waku/waku_sync/common.nim @@ -0,0 +1,32 @@ +{.push raises: [].} + +import std/[options], chronos, libp2p/peerId +import ../waku_core + +const + DefaultSyncInterval*: Duration = 5.minutes + DefaultSyncRange*: Duration = 1.hours + RetryDelay*: Duration = 30.seconds + WakuSyncCodec* = "/vac/waku/sync/1.0.0" + DefaultMaxFrameSize* = 1048576 # 1 MiB + DefaultGossipSubJitter*: Duration = 20.seconds + +type + TransferCallback* = proc( + hashes: seq[WakuMessageHash], peerId: PeerId + ): Future[Result[void, string]] {.async: (raises: []), closure.} + + PruneCallback* = proc( + startTime: Timestamp, endTime: Timestamp, cursor = none(WakuMessageHash) + ): Future[ + Result[(seq[(WakuMessageHash, Timestamp)], Option[WakuMessageHash]), string] + ] {.async: (raises: []), closure.} + + SyncPayload* = object + syncRange*: Option[(uint64, uint64)] + + frameSize*: Option[uint64] + + negentropy*: seq[byte] # negentropy protocol payload + + hashes*: seq[WakuMessageHash] diff --git a/waku/waku_sync/protocol.nim b/waku/waku_sync/protocol.nim new file mode 100644 index 0000000000..0a5e6e49d8 --- /dev/null +++ b/waku/waku_sync/protocol.nim @@ -0,0 +1,520 @@ +{.push raises: [].} + +import + std/[options, sugar, sequtils], + stew/byteutils, + results, + chronicles, + chronos, + metrics, + libp2p/utility, + libp2p/protocols/protocol, + libp2p/stream/connection, + libp2p/crypto/crypto, + eth/p2p/discoveryv5/enr +import + ../common/nimchronos, + ../common/enr, + ../waku_core, + ../waku_archive, + ../waku_store/[client, common], + ../waku_enr, + ../node/peer_manager/peer_manager, + ./raw_bindings, + ./common, + ./session + +logScope: + topics = "waku sync" + +type WakuSync* = ref object of LPProtocol + storage: NegentropyStorage + maxFrameSize: int # Negentropy param to limit the size of payloads + + peerManager: PeerManager + + syncInterval: timer.Duration # Time between each syncronisation attempt + syncRange: timer.Duration # Amount of time in the past to sync + relayJitter: Duration # Amount of time since the present to ignore when syncing + transferCallBack: Option[TransferCallback] # Callback for message transfers. + + pruneCallBack: Option[PruneCallBack] # Callback with the result of the archive query + pruneStart: Timestamp # Last pruning start timestamp + pruneOffset: timer.Duration # Offset to prune a bit more than necessary. + + periodicSyncFut: Future[void] + periodicPruneFut: Future[void] + +proc storageSize*(self: WakuSync): int = + self.storage.len + +proc messageIngress*(self: WakuSync, pubsubTopic: PubsubTopic, msg: WakuMessage) = + if msg.ephemeral: + return + + let msgHash: WakuMessageHash = computeMessageHash(pubsubTopic, msg) + + trace "inserting message into waku sync storage ", + msg_hash = msgHash.to0xHex(), timestamp = msg.timestamp + + self.storage.insert(msg.timestamp, msgHash).isOkOr: + error "failed to insert message ", msg_hash = msgHash.to0xHex(), error = $error + +proc messageIngress*( + self: WakuSync, pubsubTopic: PubsubTopic, msgHash: WakuMessageHash, msg: WakuMessage +) = + if msg.ephemeral: + return + + trace "inserting message into waku sync storage ", + msg_hash = msgHash.to0xHex(), timestamp = msg.timestamp + + if self.storage.insert(msg.timestamp, msgHash).isErr(): + error "failed to insert message ", msg_hash = msgHash.to0xHex() + +proc calculateRange( + jitter: Duration = 20.seconds, syncRange: Duration = 1.hours +): (int64, int64) = + ## Calculates the start and end time of a sync session + + var now = getNowInNanosecondTime() + + # Because of message jitter inherent to Relay protocol + now -= jitter.nanos + + let syncRange = syncRange.nanos + + let syncStart = now - syncRange + let syncEnd = now + + return (syncStart, syncEnd) + +proc request( + self: WakuSync, conn: Connection +): Future[Result[seq[WakuMessageHash], string]] {.async.} = + let (syncStart, syncEnd) = calculateRange(self.relayJitter) + + let initialized = + ?clientInitialize(self.storage, conn, self.maxFrameSize, syncStart, syncEnd) + + debug "sync session initialized", + client = self.peerManager.switch.peerInfo.peerId, + server = conn.peerId, + frameSize = self.maxFrameSize, + timeStart = syncStart, + timeEnd = syncEnd + + var hashes: seq[WakuMessageHash] + var reconciled = initialized + + while true: + let sent = ?await reconciled.send() + + trace "sync payload sent", + client = self.peerManager.switch.peerInfo.peerId, + server = conn.peerId, + payload = reconciled.payload + + let received = ?await sent.listenBack() + + trace "sync payload received", + client = self.peerManager.switch.peerInfo.peerId, + server = conn.peerId, + payload = received.payload + + reconciled = (?received.clientReconcile(hashes)).valueOr: + let completed = error # Result[Reconciled, Completed] + + ?await completed.clientTerminate() + + debug "sync session ended gracefully", + client = self.peerManager.switch.peerInfo.peerId, server = conn.peerId + + return ok(hashes) + + continue + +proc storeSynchronization*( + self: WakuSync, peerInfo: Option[RemotePeerInfo] = none(RemotePeerInfo) +): Future[Result[(seq[WakuMessageHash], RemotePeerInfo), string]] {.async.} = + let peer = peerInfo.valueOr: + self.peerManager.selectPeer(WakuSyncCodec).valueOr: + return err("No suitable peer found for sync") + + let connOpt = await self.peerManager.dialPeer(peer, WakuSyncCodec) + + let conn: Connection = connOpt.valueOr: + return err("Cannot establish sync connection") + + let hashes: seq[WakuMessageHash] = (await self.request(conn)).valueOr: + error "sync session ended", + server = self.peerManager.switch.peerInfo.peerId, client = conn.peerId, error + + return err("Sync request error: " & error) + + return ok((hashes, peer)) + +proc handleSyncSession( + self: WakuSync, conn: Connection +): Future[Result[seq[WakuMessageHash], string]] {.async.} = + let (syncStart, syncEnd) = calculateRange(self.relayJitter) + + let initialized = + ?serverInitialize(self.storage, conn, self.maxFrameSize, syncStart, syncEnd) + + var sent = initialized + + while true: + let received = ?await sent.listenBack() + + trace "sync payload received", + server = self.peerManager.switch.peerInfo.peerId, + client = conn.peerId, + payload = received.payload + + let reconciled = (?received.serverReconcile()).valueOr: + let completed = error # Result[Reconciled, Completed] + + let hashes = await completed.serverTerminate() + + return ok(hashes) + + sent = ?await reconciled.send() + + trace "sync payload sent", + server = self.peerManager.switch.peerInfo.peerId, + client = conn.peerId, + payload = reconciled.payload + + continue + +proc initProtocolHandler(self: WakuSync) = + proc handle(conn: Connection, proto: string) {.async, closure.} = + debug "sync session requested", + server = self.peerManager.switch.peerInfo.peerId, client = conn.peerId + + let hashes = (await self.handleSyncSession(conn)).valueOr: + debug "sync session ended", + server = self.peerManager.switch.peerInfo.peerId, client = conn.peerId, error + + #TODO send error code and desc to client + return + + if hashes.len > 0 and self.transferCallBack.isSome(): + let callback = self.transferCallBack.get() + + (await callback(hashes, conn.peerId)).isOkOr: + error "transfer callback failed", error = $error + + debug "sync session ended gracefully", + server = self.peerManager.switch.peerInfo.peerId, client = conn.peerId + + self.handler = handle + self.codec = WakuSyncCodec + +proc createPruneCallback( + self: WakuSync, wakuArchive: WakuArchive +): Result[PruneCallBack, string] = + if wakuArchive.isNil(): + return err ("waku archive unavailable") + + let callback: PruneCallback = proc( + pruneStart: Timestamp, pruneStop: Timestamp, cursor: Option[WakuMessageHash] + ): Future[ + Result[(seq[(WakuMessageHash, Timestamp)], Option[WakuMessageHash]), string] + ] {.async: (raises: []), closure.} = + let archiveCursor = + if cursor.isSome(): + some(cursor.get()) + else: + none(ArchiveCursor) + + let query = ArchiveQuery( + includeData: true, + cursor: archiveCursor, + startTime: some(pruneStart), + endTime: some(pruneStop), + pageSize: 100, + ) + + let catchable = catch: + await wakuArchive.findMessages(query) + + if catchable.isErr(): + return err("archive error: " & catchable.error.msg) + + let res = catchable.get() + let response = res.valueOr: + return err("archive error: " & $error) + + let elements = collect(newSeq): + for (hash, msg) in response.hashes.zip(response.messages): + (hash, msg.timestamp) + + let cursor = response.cursor + + return ok((elements, cursor)) + + return ok(callback) + +proc createTransferCallback( + self: WakuSync, wakuArchive: WakuArchive, wakuStoreClient: WakuStoreClient +): Result[TransferCallback, string] = + if wakuArchive.isNil(): + return err("waku archive unavailable") + + if wakuStoreClient.isNil(): + return err("waku store client unavailable") + + let callback: TransferCallback = proc( + hashes: seq[WakuMessageHash], peerId: PeerId + ): Future[Result[void, string]] {.async: (raises: []), closure.} = + var query = StoreQueryRequest() + query.includeData = true + query.messageHashes = hashes + query.paginationLimit = some(uint64(100)) + + while true: + let catchable = catch: + await wakuStoreClient.query(query, peerId) + + if catchable.isErr(): + return err("store client error: " & catchable.error.msg) + + let res = catchable.get() + let response = res.valueOr: + return err("store client error: " & $error) + + query.paginationCursor = response.paginationCursor + + for kv in response.messages: + let handleRes = catch: + await wakuArchive.handleMessage(kv.pubsubTopic.get(), kv.message.get()) + + if handleRes.isErr(): + error "message transfer failed", error = handleRes.error.msg + # Messages can be synced next time since they are not added to storage yet. + continue + + self.messageIngress(kv.pubsubTopic.get(), kv.messageHash, kv.message.get()) + + if query.paginationCursor.isNone(): + break + + return ok() + + return ok(callback) + +proc initFillStorage( + self: WakuSync, wakuArchive: WakuArchive +): Future[Result[void, string]] {.async.} = + if wakuArchive.isNil(): + return err("waku archive unavailable") + + let endTime = getNowInNanosecondTime() + let starTime = endTime - self.syncRange.nanos + + var query = ArchiveQuery( + includeData: true, + cursor: none(ArchiveCursor), + startTime: some(starTime), + endTime: some(endTime), + pageSize: 100, + ) + + while true: + let response = (await wakuArchive.findMessages(query)).valueOr: + return err($error) + + for i in 0 ..< response.hashes.len: + let hash = response.hashes[i] + let topic = response.topics[i] + let msg = response.messages[i] + + self.messageIngress(topic, hash, msg) + + if response.cursor.isNone(): + break + + query.cursor = response.cursor + + return ok() + +proc new*( + T: type WakuSync, + peerManager: PeerManager, + maxFrameSize: int = DefaultMaxFrameSize, + syncRange: timer.Duration = DefaultSyncRange, + syncInterval: timer.Duration = DefaultSyncInterval, + relayJitter: Duration = DefaultGossipSubJitter, + wakuArchive: WakuArchive, + wakuStoreClient: WakuStoreClient, + pruneCallback: Option[PruneCallback] = none(PruneCallback), + transferCallback: Option[TransferCallback] = none(TransferCallback), +): Future[Result[T, string]] {.async.} = + let storage = NegentropyStorage.new().valueOr: + return err("negentropy storage creation failed") + + var sync = WakuSync( + storage: storage, + peerManager: peerManager, + maxFrameSize: maxFrameSize, + syncInterval: syncInterval, + syncRange: syncRange, + relayJitter: relayJitter, + pruneOffset: syncInterval div 100, + ) + + sync.initProtocolHandler() + + sync.pruneCallBack = pruneCallback + + if sync.pruneCallBack.isNone(): + let res = sync.createPruneCallback(wakuArchive) + + if res.isErr(): + error "pruning callback creation error", error = res.error + else: + sync.pruneCallBack = some(res.get()) + + sync.transferCallBack = transferCallback + + if sync.transferCallBack.isNone(): + let res = sync.createTransferCallback(wakuArchive, wakuStoreClient) + + if res.isErr(): + error "transfer callback creation error", error = res.error + else: + sync.transferCallBack = some(res.get()) + + let res = await sync.initFillStorage(wakuArchive) + if res.isErr(): + warn "will not sync messages before this point in time", error = res.error + + info "WakuSync protocol initialized" + + return ok(sync) + +proc periodicSync(self: WakuSync, callback: TransferCallback) {.async.} = + debug "periodic sync initialized", interval = $self.syncInterval + + while true: # infinite loop + await sleepAsync(self.syncInterval) + + debug "periodic sync started" + + var + hashes: seq[WakuMessageHash] + peer: RemotePeerInfo + tries = 3 + + while true: + let res = (await self.storeSynchronization()).valueOr: + # we either try again or log an error and break + if tries > 0: + tries -= 1 + await sleepAsync(RetryDelay) + continue + else: + error "sync failed", error = $error + break + + hashes = res[0] + peer = res[1] + break + + if hashes.len > 0: + tries = 3 + while true: + (await callback(hashes, peer.peerId)).isOkOr: + # we either try again or log an error and break + if tries > 0: + tries -= 1 + await sleepAsync(RetryDelay) + continue + else: + error "transfer callback failed", error = $error + break + + break + + debug "periodic sync done", hashSynced = hashes.len + + continue + +proc periodicPrune(self: WakuSync, callback: PruneCallback) {.async.} = + debug "periodic prune initialized", interval = $self.syncInterval + + # Default T minus 60m + self.pruneStart = getNowInNanosecondTime() - self.syncRange.nanos + + await sleepAsync(self.syncInterval) + + # Default T minus 55m + var pruneStop = getNowInNanosecondTime() - self.syncRange.nanos + + while true: # infinite loop + await sleepAsync(self.syncInterval) + + debug "periodic prune started", + startTime = self.pruneStart - self.pruneOffset.nanos, + endTime = pruneStop, + storageSize = self.storage.len + + var (elements, cursor) = + (newSeq[(WakuMessageHash, Timestamp)](0), none(WakuMessageHash)) + + var tries = 3 + while true: + (elements, cursor) = ( + await callback(self.pruneStart - self.pruneOffset.nanos, pruneStop, cursor) + ).valueOr: + # we either try again or log an error and break + if tries > 0: + tries -= 1 + await sleepAsync(RetryDelay) + continue + else: + error "pruning callback failed", error = $error + break + + if elements.len == 0: + # no elements to remove, stop + break + + for (hash, timestamp) in elements: + self.storage.erase(timestamp, hash).isOkOr: + error "storage erase failed", + timestamp = timestamp, msg_hash = hash.to0xHex(), error = $error + continue + + if cursor.isNone(): + # no more pages, stop + break + + self.pruneStart = pruneStop + pruneStop = getNowInNanosecondTime() - self.syncRange.nanos + + debug "periodic prune done", storageSize = self.storage.len + + continue + +proc start*(self: WakuSync) = + self.started = true + + if self.transferCallBack.isSome() and self.syncInterval > ZeroDuration: + self.periodicSyncFut = self.periodicSync(self.transferCallBack.get()) + + if self.pruneCallBack.isSome() and self.syncInterval > ZeroDuration: + self.periodicPruneFut = self.periodicPrune(self.pruneCallBack.get()) + + info "WakuSync protocol started" + +proc stopWait*(self: WakuSync) {.async.} = + if self.transferCallBack.isSome() and self.syncInterval > ZeroDuration: + await self.periodicSyncFut.cancelAndWait() + + if self.pruneCallBack.isSome() and self.syncInterval > ZeroDuration: + await self.periodicPruneFut.cancelAndWait() + + info "WakuSync protocol stopped" diff --git a/waku/waku_sync/raw_bindings.nim b/waku/waku_sync/raw_bindings.nim new file mode 100644 index 0000000000..8bd9bac776 --- /dev/null +++ b/waku/waku_sync/raw_bindings.nim @@ -0,0 +1,499 @@ +{.push raises: [].} + +from os import DirSep + +import std/[strutils], chronicles, std/options, stew/byteutils, confutils, results +import ../waku_core/message + +const negentropyPath = + currentSourcePath.rsplit(DirSep, 1)[0] & DirSep & ".." & DirSep & ".." & DirSep & + "vendor" & DirSep & "negentropy" & DirSep & "cpp" & DirSep + +const NEGENTROPY_HEADER = negentropyPath & "negentropy_wrapper.h" + +logScope: + topics = "waku sync" + +type Buffer = object + len*: uint64 + `ptr`*: ptr uint8 + +type BindingResult = object + output: Buffer + have_ids_len: uint + need_ids_len: uint + have_ids: ptr Buffer + need_ids: ptr Buffer + error: cstring + +proc toWakuMessageHash(buffer: Buffer): WakuMessageHash = + assert buffer.len == 32 + + var hash: WakuMessageHash + + copyMem(hash[0].addr, buffer.ptr, 32) + + return hash + +proc toBuffer(x: openArray[byte]): Buffer = + ## converts the input to a Buffer object + ## the Buffer object is used to communicate data with the rln lib + var temp = @x + let baseAddr = cast[pointer](x) + let output = Buffer(`ptr`: cast[ptr uint8](baseAddr), len: uint64(temp.len)) + return output + +proc bufferToBytes(buffer: ptr Buffer, len: Option[uint64] = none(uint64)): seq[byte] = + var bufLen: uint64 + if isNone(len): + bufLen = buffer.len + else: + bufLen = len.get() + if bufLen == 0: + return @[] + trace "length of buffer is", len = bufLen + let bytes = newSeq[byte](bufLen) + copyMem(bytes[0].unsafeAddr, buffer.ptr, bufLen) + return bytes + +proc toBufferSeq(buffLen: uint, buffPtr: ptr Buffer): seq[Buffer] = + var uncheckedArr = cast[ptr UncheckedArray[Buffer]](buffPtr) + var mySequence = newSeq[Buffer](buffLen) + for i in 0 .. buffLen - 1: + mySequence[i] = uncheckedArr[i] + return mySequence + +### Storage ### + +type NegentropyStorage* = distinct pointer + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L27 +proc storage_init( + db_path: cstring, name: cstring +): NegentropyStorage {.header: NEGENTROPY_HEADER, importc: "storage_new".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L41 +proc raw_insert( + storage: NegentropyStorage, timestamp: uint64, id: ptr Buffer +): bool {.header: NEGENTROPY_HEADER, importc: "storage_insert".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L43 +proc raw_erase( + storage: NegentropyStorage, timestamp: uint64, id: ptr Buffer +): bool {.header: NEGENTROPY_HEADER, importc: "storage_erase".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L29 +proc free( + storage: NegentropyStorage +) {.header: NEGENTROPY_HEADER, importc: "storage_delete".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L31 +proc size( + storage: NegentropyStorage +): cint {.header: NEGENTROPY_HEADER, importc: "storage_size".} + +### Negentropy ### + +type RawNegentropy* = distinct pointer + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L33 +proc constructNegentropy( + storage: NegentropyStorage, frameSizeLimit: uint64 +): RawNegentropy {.header: NEGENTROPY_HEADER, importc: "negentropy_new".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L37 +proc raw_initiate( + negentropy: RawNegentropy, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "negentropy_initiate".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L39 +proc raw_setInitiator( + negentropy: RawNegentropy +) {.header: NEGENTROPY_HEADER, importc: "negentropy_setinitiator".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L45 +proc raw_reconcile( + negentropy: RawNegentropy, query: ptr Buffer, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "reconcile".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L51 +proc raw_reconcile_with_ids( + negentropy: RawNegentropy, query: ptr Buffer, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "reconcile_with_ids_no_cbk".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L35 +proc free( + negentropy: RawNegentropy +) {.header: NEGENTROPY_HEADER, importc: "negentropy_delete".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L53 +proc free_result( + r: ptr BindingResult +) {.header: NEGENTROPY_HEADER, importc: "free_result".} + +### SubRange ### + +type NegentropySubRangeStorage* = distinct pointer + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L57 +proc subrange_init( + storage: NegentropyStorage, startTimestamp: uint64, endTimestamp: uint64 +): NegentropySubRangeStorage {.header: NEGENTROPY_HEADER, importc: "subrange_new".} + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L59 +proc free( + subrange: NegentropySubRangeStorage +) {.header: NEGENTROPY_HEADER, importc: "subrange_delete".} + +# https://github.com/waku-org/negentropy/blob/d4845b95b5a2d9bee28555833e7502db71bf319f/cpp/negentropy_wrapper.h#L31 +proc size( + subrange: NegentropySubRangeStorage +): cint {.header: NEGENTROPY_HEADER, importc: "subrange_size".} + +### Negentropy with NegentropySubRangeStorage ### + +type RawNegentropySubRange = distinct pointer + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L61 +proc constructNegentropyWithSubRange( + subrange: NegentropySubRangeStorage, frameSizeLimit: uint64 +): RawNegentropySubRange {. + header: NEGENTROPY_HEADER, importc: "negentropy_subrange_new" +.} + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L65 +proc raw_initiate_subrange( + negentropy: RawNegentropySubRange, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "negentropy_subrange_initiate".} + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L67 +proc raw_reconcile_subrange( + negentropy: RawNegentropySubRange, query: ptr Buffer, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "reconcile_subrange".} + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L69 +proc raw_reconcile_with_ids_subrange( + negentropy: RawNegentropySubRange, query: ptr Buffer, r: ptr BindingResult +): int {.header: NEGENTROPY_HEADER, importc: "reconcile_with_ids_subrange_no_cbk".} + +# https://github.com/waku-org/negentropy/blob/3044a30e4ba2e218aee6dee2ef5b4a4b6f144865/cpp/negentropy_wrapper.h#L63 +proc free( + negentropy: RawNegentropySubRange +) {.header: NEGENTROPY_HEADER, importc: "negentropy_subrange_delete".} + +### Wrappings ### + +### Storage ### + +proc `==`*(a: NegentropyStorage, b: pointer): bool {.borrow.} + +proc new*(T: type NegentropyStorage): Result[T, string] = + #TODO db name and path + let storage = storage_init("", "") + + #[ TODO: Uncomment once we move to lmdb + if storage == nil: + return err("storage initialization failed") ]# + return ok(storage) + +proc delete*(storage: NegentropyStorage) = + storage.free() + +proc erase*( + storage: NegentropyStorage, id: int64, hash: WakuMessageHash +): Result[void, string] = + var buffer = toBuffer(hash) + var bufPtr = addr(buffer) + let res = raw_erase(storage, uint64(id), bufPtr) + + #TODO error handling once we move to lmdb + + if res: + return ok() + else: + return err("erase error") + +proc insert*( + storage: NegentropyStorage, id: int64, hash: WakuMessageHash +): Result[void, string] = + var buffer = toBuffer(hash) + var bufPtr = addr(buffer) + let res = raw_insert(storage, uint64(id), bufPtr) + + #TODO error handling once we move to lmdb + + if res: + return ok() + else: + return err("insert error") + +proc len*(storage: NegentropyStorage): int = + int(storage.size) + +### SubRange ### + +proc `==`*(a: NegentropySubRangeStorage, b: pointer): bool {.borrow.} + +proc new*( + T: type NegentropySubRangeStorage, + storage: NegentropyStorage, + startTime: uint64 = uint64.low, + endTime: uint64 = uint64.high, +): Result[T, string] = + let subrange = subrange_init(storage, startTime, endTime) + + #[ TODO: Uncomment once we move to lmdb + if storage == nil: + return err("storage initialization failed") ]# + return ok(subrange) + +proc delete*(subrange: NegentropySubRangeStorage) = + subrange.free() + +proc len*(subrange: NegentropySubRangeStorage): int = + int(subrange.size) + +### Interface ### + +type + Negentropy* = ref object of RootObj + + NegentropyWithSubRange = ref object of Negentropy + inner: RawNegentropySubRange + + NegentropyWithStorage = ref object of Negentropy + inner: RawNegentropy + + NegentropyPayload* = distinct seq[byte] + +method delete*(self: Negentropy) {.base, gcsafe.} = + discard + +method initiate*(self: Negentropy): Result[NegentropyPayload, string] {.base.} = + discard + +method serverReconcile*( + self: Negentropy, query: NegentropyPayload +): Result[NegentropyPayload, string] {.base.} = + discard + +method clientReconcile*( + self: Negentropy, + query: NegentropyPayload, + haves: var seq[WakuMessageHash], + needs: var seq[WakuMessageHash], +): Result[Option[NegentropyPayload], string] {.base.} = + discard + +### Impl. ### + +proc new*( + T: type Negentropy, + storage: NegentropyStorage | NegentropySubRangeStorage, + frameSizeLimit: int, +): Result[T, string] = + if storage is NegentropyStorage: + let raw_negentropy = + constructNegentropy(NegentropyStorage(storage), uint64(frameSizeLimit)) + + let negentropy = NegentropyWithStorage(inner: raw_negentropy) + + return ok(negentropy) + elif storage is NegentropySubRangeStorage: + let raw_negentropy = constructNegentropyWithSubRange( + NegentropySubRangeStorage(storage), uint64(frameSizeLimit) + ) + + let negentropy = NegentropyWithSubRange(inner: raw_negentropy) + + return ok(negentropy) + +method delete*(self: NegentropyWithSubRange) = + self.inner.free() + +method initiate*(self: NegentropyWithSubRange): Result[NegentropyPayload, string] = + ## Client inititate a sync session with a server by sending a payload + var myResult {.noinit.}: BindingResult = BindingResult() + var myResultPtr = addr myResult + + let ret = self.inner.raw_initiate_subrange(myResultPtr) + if ret < 0 or myResultPtr == nil: + error "negentropy initiate failed with code ", code = ret + return err("negentropy already initiated!") + let bytes: seq[byte] = bufferToBytes(addr(myResultPtr.output)) + free_result(myResultPtr) + trace "received return from initiate", len = myResultPtr.output.len + + return ok(NegentropyPayload(bytes)) + +method serverReconcile*( + self: NegentropyWithSubRange, query: NegentropyPayload +): Result[NegentropyPayload, string] = + ## Server response to a negentropy payload. + ## Always return an answer. + + let queryBuf = toBuffer(seq[byte](query)) + var queryBufPtr = queryBuf.unsafeAddr #TODO: Figure out why addr(buffer) throws error + var myResult {.noinit.}: BindingResult = BindingResult() + var myResultPtr = addr myResult + + let ret = self.inner.raw_reconcile_subrange(queryBufPtr, myResultPtr) + if ret < 0: + error "raw_reconcile failed with code ", code = ret + return err($myResultPtr.error) + trace "received return from raw_reconcile", len = myResultPtr.output.len + + let outputBytes: seq[byte] = bufferToBytes(addr(myResultPtr.output)) + trace "outputBytes len", len = outputBytes.len + free_result(myResultPtr) + + return ok(NegentropyPayload(outputBytes)) + +method clientReconcile*( + self: NegentropyWithSubRange, + query: NegentropyPayload, + haves: var seq[WakuMessageHash], + needs: var seq[WakuMessageHash], +): Result[Option[NegentropyPayload], string] = + ## Client response to a negentropy payload. + ## May return an answer, if not the sync session done. + + let cQuery = toBuffer(seq[byte](query)) + + var myResult {.noinit.}: BindingResult = BindingResult() + myResult.have_ids_len = 0 + myResult.need_ids_len = 0 + var myResultPtr = addr myResult + + let ret = self.inner.raw_reconcile_with_ids_subrange(cQuery.unsafeAddr, myResultPtr) + if ret < 0: + error "raw_reconcile failed with code ", code = ret + return err($myResultPtr.error) + + let output = bufferToBytes(addr myResult.output) + + var + have_hashes: seq[Buffer] + need_hashes: seq[Buffer] + + if myResult.have_ids_len > 0: + have_hashes = toBufferSeq(myResult.have_ids_len, myResult.have_ids) + if myResult.need_ids_len > 0: + need_hashes = toBufferSeq(myResult.need_ids_len, myResult.need_ids) + + trace "have and need hashes ", + have_count = have_hashes.len, need_count = need_hashes.len + + for i in 0 .. have_hashes.len - 1: + var hash = toWakuMessageHash(have_hashes[i]) + trace "have hashes ", index = i, msg_hash = hash.to0xHex() + haves.add(hash) + + for i in 0 .. need_hashes.len - 1: + var hash = toWakuMessageHash(need_hashes[i]) + trace "need hashes ", index = i, msg_hash = hash.to0xHex() + needs.add(hash) + + trace "return ", output = output, len = output.len + + free_result(myResultPtr) + + if output.len < 1: + return ok(none(NegentropyPayload)) + + return ok(some(NegentropyPayload(output))) + +method delete*(self: NegentropyWithStorage) = + self.inner.free() + +method initiate*(self: NegentropyWithStorage): Result[NegentropyPayload, string] = + ## Client inititate a sync session with a server by sending a payload + var myResult {.noinit.}: BindingResult = BindingResult() + var myResultPtr = addr myResult + + let ret = self.inner.raw_initiate(myResultPtr) + if ret < 0 or myResultPtr == nil: + error "negentropy initiate failed with code ", code = ret + return err("negentropy already initiated!") + let bytes: seq[byte] = bufferToBytes(addr(myResultPtr.output)) + free_result(myResultPtr) + trace "received return from initiate", len = myResultPtr.output.len + + return ok(NegentropyPayload(bytes)) + +method serverReconcile*( + self: NegentropyWithStorage, query: NegentropyPayload +): Result[NegentropyPayload, string] = + ## Server response to a negentropy payload. + ## Always return an answer. + + let queryBuf = toBuffer(seq[byte](query)) + var queryBufPtr = queryBuf.unsafeAddr #TODO: Figure out why addr(buffer) throws error + var myResult {.noinit.}: BindingResult = BindingResult() + var myResultPtr = addr myResult + + let ret = self.inner.raw_reconcile(queryBufPtr, myResultPtr) + if ret < 0: + error "raw_reconcile failed with code ", code = ret + return err($myResultPtr.error) + trace "received return from raw_reconcile", len = myResultPtr.output.len + + let outputBytes: seq[byte] = bufferToBytes(addr(myResultPtr.output)) + trace "outputBytes len", len = outputBytes.len + free_result(myResultPtr) + + return ok(NegentropyPayload(outputBytes)) + +method clientReconcile*( + self: NegentropyWithStorage, + query: NegentropyPayload, + haves: var seq[WakuMessageHash], + needs: var seq[WakuMessageHash], +): Result[Option[NegentropyPayload], string] = + ## Client response to a negentropy payload. + ## May return an answer, if not the sync session done. + + let cQuery = toBuffer(seq[byte](query)) + + var myResult {.noinit.}: BindingResult = BindingResult() + myResult.have_ids_len = 0 + myResult.need_ids_len = 0 + var myResultPtr = addr myResult + + let ret = self.inner.raw_reconcile_with_ids(cQuery.unsafeAddr, myResultPtr) + if ret < 0: + error "raw_reconcile failed with code ", code = ret + return err($myResultPtr.error) + + let output = bufferToBytes(addr myResult.output) + + var + have_hashes: seq[Buffer] + need_hashes: seq[Buffer] + + if myResult.have_ids_len > 0: + have_hashes = toBufferSeq(myResult.have_ids_len, myResult.have_ids) + if myResult.need_ids_len > 0: + need_hashes = toBufferSeq(myResult.need_ids_len, myResult.need_ids) + + trace "have and need hashes ", + have_count = have_hashes.len, need_count = need_hashes.len + + for i in 0 .. have_hashes.len - 1: + var hash = toWakuMessageHash(have_hashes[i]) + trace "have hashes ", index = i, msg_hash = hash.to0xHex() + haves.add(hash) + + for i in 0 .. need_hashes.len - 1: + var hash = toWakuMessageHash(need_hashes[i]) + trace "need hashes ", index = i, msg_hash = hash.to0xHex() + needs.add(hash) + + trace "return ", output = output, len = output.len + + free_result(myResultPtr) + + if output.len < 1: + return ok(none(NegentropyPayload)) + + return ok(some(NegentropyPayload(output))) diff --git a/waku/waku_sync/session.nim b/waku/waku_sync/session.nim new file mode 100644 index 0000000000..ff6d741efe --- /dev/null +++ b/waku/waku_sync/session.nim @@ -0,0 +1,240 @@ +{.push raises: [].} + +import std/options, results, chronos, libp2p/stream/connection + +import + ../common/nimchronos, + ../common/protobuf, + ../waku_core, + ./raw_bindings, + ./common, + ./codec + +#TODO add states for protocol negotiation + +### Type State ### + +type ClientSync* = object + haveHashes: seq[WakuMessageHash] + +type ServerSync* = object + +# T is either ClientSync or ServerSync + +type Reconciled*[T] = object + sync: T + negentropy: Negentropy + connection: Connection + frameSize: int + payload*: SyncPayload + +type Sent*[T] = object + sync: T + negentropy: Negentropy + connection: Connection + frameSize: int + +type Received*[T] = object + sync: T + negentropy: Negentropy + connection: Connection + frameSize: int + payload*: SyncPayload + +type Completed*[T] = object + sync: T + negentropy: Negentropy + connection: Connection + haveHashes: seq[WakuMessageHash] + +### State Transition ### + +proc clientInitialize*( + store: NegentropyStorage, + conn: Connection, + frameSize = DefaultMaxFrameSize, + start = int64.low, + `end` = int64.high, +): Result[Reconciled[ClientSync], string] = + let subrange = ?NegentropySubRangeStorage.new(store, uint64(start), uint64(`end`)) + + let negentropy = ?Negentropy.new(subrange, frameSize) + + let negentropyPayload = ?negentropy.initiate() + + let payload = SyncPayload(negentropy: seq[byte](negentropyPayload)) + + let sync = ClientSync() + + return ok( + Reconciled[ClientSync]( + sync: sync, + negentropy: negentropy, + connection: conn, + frameSize: frameSize, + payload: payload, + ) + ) + +proc serverInitialize*( + store: NegentropyStorage, + conn: Connection, + frameSize = DefaultMaxFrameSize, + syncStart = int64.low, + syncEnd = int64.high, +): Result[Sent[ServerSync], string] = + let subrange = + ?NegentropySubRangeStorage.new(store, uint64(syncStart), uint64(syncEnd)) + + let negentropy = ?Negentropy.new(subrange, frameSize) + + let sync = ServerSync() + + return ok( + Sent[ServerSync]( + sync: sync, negentropy: negentropy, connection: conn, frameSize: frameSize + ) + ) + +proc send*[T](self: Reconciled[T]): Future[Result[Sent[T], string]] {.async.} = + let writeRes = catch: + await self.connection.writeLP(self.payload.encode().buffer) + + if writeRes.isErr(): + return err("send connection write error: " & writeRes.error.msg) + + return ok( + Sent[T]( + sync: self.sync, + negentropy: self.negentropy, + connection: self.connection, + frameSize: self.frameSize, + ) + ) + +proc listenBack*[T](self: Sent[T]): Future[Result[Received[T], string]] {.async.} = + let readRes = catch: + await self.connection.readLp(-1) + + let buffer: seq[byte] = + if readRes.isOk(): + readRes.get() + else: + return err("listenBack connection read error: " & readRes.error.msg) + + # can't otherwise the compiler complains + #let payload = SyncPayload.decode(buffer).valueOr: + #return err($error) + + let decodeRes = SyncPayload.decode(buffer) + + let payload = + if decodeRes.isOk(): + decodeRes.get() + else: + let decodeError: ProtobufError = decodeRes.error + let errMsg = $decodeError + return err("listenBack decoding error: " & errMsg) + + return ok( + Received[T]( + sync: self.sync, + negentropy: self.negentropy, + connection: self.connection, + frameSize: self.frameSize, + payload: payload, + ) + ) + +# Aliasing for readability +type ContinueOrCompleted[T] = Result[Reconciled[T], Completed[T]] +type Continue[T] = Reconciled[T] + +proc clientReconcile*( + self: Received[ClientSync], needHashes: var seq[WakuMessageHash] +): Result[ContinueOrCompleted[ClientSync], string] = + var haves = self.sync.haveHashes + + let responseOpt = + ?self.negentropy.clientReconcile( + NegentropyPayload(self.payload.negentropy), haves, needHashes + ) + + let sync = ClientSync(haveHashes: haves) + + let response = responseOpt.valueOr: + let res = ContinueOrCompleted[ClientSync].err( + Completed[ClientSync]( + sync: sync, negentropy: self.negentropy, connection: self.connection + ) + ) + + return ok(res) + + let payload = SyncPayload(negentropy: seq[byte](response)) + + let res = ContinueOrCompleted[ClientSync].ok( + Continue[ClientSync]( + sync: sync, + negentropy: self.negentropy, + connection: self.connection, + frameSize: self.frameSize, + payload: payload, + ) + ) + + return ok(res) + +proc serverReconcile*( + self: Received[ServerSync] +): Result[ContinueOrCompleted[ServerSync], string] = + if self.payload.negentropy.len == 0: + let res = ContinueOrCompleted[ServerSync].err( + Completed[ServerSync]( + sync: self.sync, + negentropy: self.negentropy, + connection: self.connection, + haveHashes: self.payload.hashes, + ) + ) + + return ok(res) + + let response = + ?self.negentropy.serverReconcile(NegentropyPayload(self.payload.negentropy)) + + let payload = SyncPayload(negentropy: seq[byte](response)) + + let res = ContinueOrCompleted[ServerSync].ok( + Continue[ServerSync]( + sync: self.sync, + negentropy: self.negentropy, + connection: self.connection, + frameSize: self.frameSize, + payload: payload, + ) + ) + + return ok(res) + +proc clientTerminate*( + self: Completed[ClientSync] +): Future[Result[void, string]] {.async.} = + let payload = SyncPayload(hashes: self.sync.haveHashes) + + let writeRes = catch: + await self.connection.writeLp(payload.encode().buffer) + + if writeRes.isErr(): + return err("clientTerminate connection write error: " & writeRes.error.msg) + + self.negentropy.delete() + + return ok() + +proc serverTerminate*( + self: Completed[ServerSync] +): Future[seq[WakuMessageHash]] {.async.} = + self.negentropy.delete() + + return self.haveHashes diff --git a/waku/waku_sync/storage_manager.nim b/waku/waku_sync/storage_manager.nim new file mode 100644 index 0000000000..528da2b64c --- /dev/null +++ b/waku/waku_sync/storage_manager.nim @@ -0,0 +1,76 @@ +# Unused yet. Kept for future use. +#[ when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import std/[times, tables, options], chronicles, chronos, stew/results + +import ./raw_bindings, ../waku_core/time + +logScope: + topics = "waku sync" + +type WakuSyncStorageManager* = ref object + storages: OrderedTable[string, Storage] + # Map of dateTime and Storage objects. DateTime is of the format YYYYMMDDHH + maxHours: int64 + +proc new*( + T: type WakuSyncStorageManager, + hoursToStore: times.Duration = initDuration(minutes = 120), +): T = + return WakuSyncStorageManager(maxHours: hoursToStore.inHours) + +proc getRecentStorage*(self: WakuSyncStorageManager): Result[Option[Storage], string] = + if self.storages.len() == 0: + return ok(none(Storage)) + var storageToFetch: Storage + #is there a more effective way to fetch last element? + for k, storage in self.storages: + storageToFetch = storage + + return ok(some(storageToFetch)) + +proc deleteOldestStorage*(self: WakuSyncStorageManager) = + var storageToDelete: Storage + var time: string + #is there a more effective way to fetch first element? + for k, storage in self.storages: + storageToDelete = storage + time = k + break + + if self.storages.pop(time, storageToDelete): + delete(storageToDelete) + +proc retrieveStorage*( + self: WakuSyncStorageManager, time: Timestamp +): Result[Option[Storage], string] = + var timestamp: Timestamp + if time == 0: + timestamp = timestampInSeconds(getNowInNanosecondTime()) + debug "timestamp not provided, using now to fetch storage", timestamp = timestamp + else: + timestamp = timestampInSeconds(time) + let tsTime = times.fromUnix(timestamp) + let dateTime = times.format(tsTime, "yyyyMMddHH", utc()) + + var storage: Storage = self.storages.getOrDefault(dateTime) + if storage == nil: + #create a new storage + # TODO: May need synchronization?? + # Limit number of storages to configured duration + let hours = self.storages.len() + if hours == self.maxHours: + #Need to delete oldest storage at this point, but what if that is being synced? + self.deleteOldestStorage() + info "number of storages reached, deleting the oldest" + info "creating a new storage for ", time = dateTime + storage = Storage.new().valueOr: + error "storage creation failed" + return err(error) + self.storages[dateTime] = storage + + return ok(some(storage)) + ]#