From 2c2d8e1c1512921353cde7582a1eb7faf2417423 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Thu, 5 Feb 2026 00:30:16 +0000 Subject: [PATCH 1/8] chore: update license files to comply with Logos licensing requirements --- LICENSE-APACHE-2.0 => LICENSE-APACHE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE-APACHE-2.0 => LICENSE-APACHE (100%) diff --git a/LICENSE-APACHE-2.0 b/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE-2.0 rename to LICENSE-APACHE From 6421685ecad9039c4252c9b362531d54801715cc Mon Sep 17 00:00:00 2001 From: Darshan <35736874+darshankabariya@users.noreply.github.com> Date: Wed, 11 Feb 2026 03:00:57 +0530 Subject: [PATCH 2/8] chore: bump v0.38.0 (#3712) --- .github/workflows/windows-build.yml | 9 +- Makefile | 1 + scripts/build_rln.sh | 58 ++++------ scripts/install_nasm_in_windows.sh | 37 +++++++ scripts/regenerate_anvil_state.sh | 104 ++++++++++++++++++ tests/testlib/wakunode.nim | 4 +- tests/waku_core/test_message_digest.nim | 8 +- ...ployed-contracts-mint-and-approved.json.gz | Bin 118346 -> 118972 bytes tests/waku_store/test_wakunode_store.nim | 6 +- vendor/nim-dnsdisc | 2 +- vendor/nim-faststreams | 2 +- vendor/nim-http-utils | 2 +- vendor/nim-json-serialization | 2 +- vendor/nim-libp2p | 2 +- vendor/nim-lsquic | 2 +- vendor/nim-metrics | 2 +- vendor/nim-presto | 2 +- vendor/nim-serialization | 2 +- vendor/nim-sqlite3-abi | 2 +- vendor/nim-stew | 2 +- vendor/nim-testutils | 2 +- vendor/nim-toml-serialization | 2 +- vendor/nim-unittest2 | 2 +- vendor/nim-websock | 2 +- vendor/waku-rlnv2-contract | 2 +- vendor/zerokit | 2 +- .../send_service/relay_processor.nim | 4 +- waku/utils/requests.nim | 2 +- .../postgres_driver/postgres_driver.nim | 14 +-- .../postgres_driver/postgres_driver.nim | 14 +-- waku/waku_filter_v2/client.nim | 2 +- waku/waku_rln_relay/rln_relay.nim | 2 +- waku/waku_store_sync/reconciliation.nim | 11 +- 33 files changed, 225 insertions(+), 85 deletions(-) create mode 100644 scripts/install_nasm_in_windows.sh create mode 100755 scripts/regenerate_anvil_state.sh diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index ed6d2cb17..9c1b1eab0 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -33,6 +33,7 @@ jobs: make cmake upx + unzip mingw-w64-x86_64-rust mingw-w64-x86_64-postgresql mingw-w64-x86_64-gcc @@ -44,6 +45,12 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-llvm mingw-w64-x86_64-clang + mingw-w64-x86_64-nasm + + - name: Manually install nasm + run: | + bash scripts/install_nasm_in_windows.sh + source $HOME/.bashrc - name: Add UPX to PATH run: | @@ -54,7 +61,7 @@ jobs: - name: Verify dependencies run: | - which upx gcc g++ make cmake cargo rustc python + which upx gcc g++ make cmake cargo rustc python nasm - name: Updating submodules run: git submodule update --init --recursive diff --git a/Makefile b/Makefile index 13882253e..6457b3c0f 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ ifeq ($(detected_OS),Windows) LIBS = -lws2_32 -lbcrypt -liphlpapi -luserenv -lntdll -lminiupnpc -lnatpmp -lpq NIM_PARAMS += $(foreach lib,$(LIBS),--passL:"$(lib)") + NIM_PARAMS += --passL:"-Wl,--allow-multiple-definition" export PATH := /c/msys64/usr/bin:/c/msys64/mingw64/bin:/c/msys64/usr/lib:/c/msys64/mingw64/lib:$(PATH) diff --git a/scripts/build_rln.sh b/scripts/build_rln.sh index 1a8b63177..b36ebe807 100755 --- a/scripts/build_rln.sh +++ b/scripts/build_rln.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# This script is used to build the rln library for the current platform, or download it from the -# release page if it is available. +# This script is used to build the rln library for the current platform. +# Previously downloaded prebuilt binaries, but due to compatibility issues +# we now always build from source. set -e @@ -14,41 +15,26 @@ output_filename=$3 [[ -z "${rln_version}" ]] && { echo "No rln version specified"; exit 1; } [[ -z "${output_filename}" ]] && { echo "No output filename specified"; exit 1; } -# Get the host triplet -host_triplet=$(rustc --version --verbose | awk '/host:/{print $2}') +echo "Building RLN library from source (version ${rln_version})..." -tarball="${host_triplet}" -tarball+="-stateless" -tarball+="-rln.tar.gz" +# Check if submodule version = version in Makefile +cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" -# Download the prebuilt rln library if it is available -if curl --silent --fail-with-body -L \ - "https://github.com/vacp2p/zerokit/releases/download/$rln_version/$tarball" \ - -o "${tarball}"; -then - echo "Downloaded ${tarball}" - tar -xzf "${tarball}" - mv "release/librln.a" "${output_filename}" - rm -rf "${tarball}" release +detected_OS=$(uname -s) +if [[ "$detected_OS" == MINGW* || "$detected_OS" == MSYS* ]]; then + submodule_version=$(cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" | sed -n 's/.*"name":"rln","version":"\([^"]*\)".*/\1/p') else - echo "Failed to download ${tarball}" - # Build rln instead - # first, check if submodule version = version in Makefile - cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" - - detected_OS=$(uname -s) - if [[ "$detected_OS" == MINGW* || "$detected_OS" == MSYS* ]]; then - submodule_version=$(cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" | sed -n 's/.*"name":"rln","version":"\([^"]*\)".*/\1/p') - else - submodule_version=$(cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" | jq -r '.packages[] | select(.name == "rln") | .version') - fi - - if [[ "v${submodule_version}" != "${rln_version}" ]]; then - echo "Submodule version (v${submodule_version}) does not match version in Makefile (${rln_version})" - echo "Please update the submodule to ${rln_version}" - exit 1 - fi - # if submodule version = version in Makefile, build rln - cargo build --release -p rln --manifest-path "${build_dir}/rln/Cargo.toml" - cp "${build_dir}/target/release/librln.a" "${output_filename}" + submodule_version=$(cargo metadata --format-version=1 --no-deps --manifest-path "${build_dir}/rln/Cargo.toml" | jq -r '.packages[] | select(.name == "rln") | .version') fi + +if [[ "v${submodule_version}" != "${rln_version}" ]]; then + echo "Submodule version (v${submodule_version}) does not match version in Makefile (${rln_version})" + echo "Please update the submodule to ${rln_version}" + exit 1 +fi + +# Build rln from source +cargo build --release -p rln --manifest-path "${build_dir}/rln/Cargo.toml" +cp "${build_dir}/target/release/librln.a" "${output_filename}" + +echo "Successfully built ${output_filename}" diff --git a/scripts/install_nasm_in_windows.sh b/scripts/install_nasm_in_windows.sh new file mode 100644 index 000000000..2bba5ecd4 --- /dev/null +++ b/scripts/install_nasm_in_windows.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env sh +set -e + +NASM_VERSION="2.16.01" +NASM_ZIP="nasm-${NASM_VERSION}-win64.zip" +NASM_URL="https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/win64/${NASM_ZIP}" + +INSTALL_DIR="$HOME/.local/nasm" +BIN_DIR="$INSTALL_DIR/bin" + +echo "Installing NASM ${NASM_VERSION}..." + +# Create directories +mkdir -p "$BIN_DIR" +cd "$INSTALL_DIR" + +# Download +if [ ! -f "$NASM_ZIP" ]; then + echo "Downloading NASM..." + curl -LO "$NASM_URL" +fi + +# Extract +echo "Extracting..." +unzip -o "$NASM_ZIP" + +# Move binaries +cp nasm-*/nasm.exe "$BIN_DIR/" +cp nasm-*/ndisasm.exe "$BIN_DIR/" + +# Add to PATH in bashrc (idempotent) +if ! grep -q 'nasm/bin' "$HOME/.bashrc"; then + echo '' >> "$HOME/.bashrc" + echo '# NASM' >> "$HOME/.bashrc" + echo 'export PATH="$HOME/.local/nasm/bin:$PATH"' >> "$HOME/.bashrc" +fi + diff --git a/scripts/regenerate_anvil_state.sh b/scripts/regenerate_anvil_state.sh new file mode 100755 index 000000000..9474591d9 --- /dev/null +++ b/scripts/regenerate_anvil_state.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +# Simple script to regenerate the Anvil state file +# This creates a state file compatible with the current Foundry version + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +STATE_DIR="$PROJECT_ROOT/tests/waku_rln_relay/anvil_state" +STATE_FILE="$STATE_DIR/state-deployed-contracts-mint-and-approved.json" +STATE_FILE_GZ="${STATE_FILE}.gz" + +echo "===================================" +echo "Anvil State File Regeneration Tool" +echo "===================================" +echo "" + +# Check if Foundry is installed +if ! command -v anvil &> /dev/null; then + echo "ERROR: anvil is not installed!" + echo "Please run: make rln-deps" + exit 1 +fi + +ANVIL_VERSION=$(anvil --version 2>/dev/null | head -n1) +echo "Using Foundry: $ANVIL_VERSION" +echo "" + +# Backup existing state file +if [ -f "$STATE_FILE_GZ" ]; then + BACKUP_FILE="${STATE_FILE_GZ}.backup-$(date +%Y%m%d-%H%M%S)" + echo "Backing up existing state file to: $(basename $BACKUP_FILE)" + cp "$STATE_FILE_GZ" "$BACKUP_FILE" +fi + +# Remove old state files +rm -f "$STATE_FILE" "$STATE_FILE_GZ" + +echo "" +echo "Running test to generate fresh state file..." +echo "This will:" +echo " 1. Build RLN library" +echo " 2. Start Anvil with state dump enabled" +echo " 3. Deploy contracts" +echo " 4. Save state and compress it" +echo "" + +cd "$PROJECT_ROOT" + +# Run a single test that deploys contracts +# The test framework will handle state dump +make test tests/waku_rln_relay/test_rln_group_manager_onchain.nim "RLN instances" || { + echo "" + echo "Test execution completed (exit status: $?)" + echo "Checking if state file was generated..." +} + +# Check if state file was created +if [ -f "$STATE_FILE" ]; then + echo "" + echo "✓ State file generated: $STATE_FILE" + + # Compress it + gzip -c "$STATE_FILE" > "$STATE_FILE_GZ" + echo "✓ Compressed: $STATE_FILE_GZ" + + # File sizes + STATE_SIZE=$(du -h "$STATE_FILE" | cut -f1) + GZ_SIZE=$(du -h "$STATE_FILE_GZ" | cut -f1) + echo "" + echo "File sizes:" + echo " Uncompressed: $STATE_SIZE" + echo " Compressed: $GZ_SIZE" + + # Optionally remove uncompressed + echo "" + read -p "Remove uncompressed state file? [y/N] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm "$STATE_FILE" + echo "✓ Removed uncompressed file" + fi + + echo "" + echo "============================================" + echo "✓ SUCCESS! State file regenerated" + echo "============================================" + echo "" + echo "Next steps:" + echo " 1. Test locally: make test tests/node/test_wakunode_lightpush.nim" + echo " 2. If tests pass, commit: git add $STATE_FILE_GZ" + echo " 3. Push and verify CI passes" + echo "" +else + echo "" + echo "============================================" + echo "✗ ERROR: State file was not generated" + echo "============================================" + echo "" + echo "The state file should have been created at: $STATE_FILE" + echo "Please check the test output above for errors." + exit 1 +fi diff --git a/tests/testlib/wakunode.nim b/tests/testlib/wakunode.nim index 36aacce03..f59546ec8 100644 --- a/tests/testlib/wakunode.nim +++ b/tests/testlib/wakunode.nim @@ -27,7 +27,7 @@ import # TODO: migrate to usage of a test cluster conf proc defaultTestWakuConfBuilder*(): WakuConfBuilder = var builder = WakuConfBuilder.init() - builder.withP2pTcpPort(Port(60000)) + builder.withP2pTcpPort(Port(0)) builder.withP2pListenAddress(parseIpAddress("0.0.0.0")) builder.restServerConf.withListenAddress(parseIpAddress("127.0.0.1")) builder.withDnsAddrsNameServers( @@ -80,7 +80,7 @@ proc newTestWakuNode*( # Update extPort to default value if it's missing and there's an extIp or a DNS domain let extPort = if (extIp.isSome() or dns4DomainName.isSome()) and extPort.isNone(): - some(Port(60000)) + some(Port(0)) else: extPort diff --git a/tests/waku_core/test_message_digest.nim b/tests/waku_core/test_message_digest.nim index 1d1f71225..22a10d84d 100644 --- a/tests/waku_core/test_message_digest.nim +++ b/tests/waku_core/test_message_digest.nim @@ -35,7 +35,7 @@ suite "Waku Message - Deterministic hashing": byteutils.toHex(message.payload) == "010203045445535405060708" byteutils.toHex(message.meta) == "" byteutils.toHex(toBytesBE(uint64(message.timestamp))) == "175789bfa23f8400" - messageHash.toHex() == + byteutils.toHex(messageHash) == "cccab07fed94181c83937c8ca8340c9108492b7ede354a6d95421ad34141fd37" test "digest computation - meta field (12 bytes)": @@ -69,7 +69,7 @@ suite "Waku Message - Deterministic hashing": byteutils.toHex(message.payload) == "010203045445535405060708" byteutils.toHex(message.meta) == "73757065722d736563726574" byteutils.toHex(toBytesBE(uint64(message.timestamp))) == "175789bfa23f8400" - messageHash.toHex() == + byteutils.toHex(messageHash) == "b9b4852f9d8c489846e8bfc6c5ca6a1a8d460a40d28832a966e029eb39619199" test "digest computation - meta field (64 bytes)": @@ -104,7 +104,7 @@ suite "Waku Message - Deterministic hashing": byteutils.toHex(message.meta) == "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" byteutils.toHex(toBytesBE(uint64(message.timestamp))) == "175789bfa23f8400" - messageHash.toHex() == + byteutils.toHex(messageHash) == "653460d04f66c5b11814d235152f4f246e6f03ef80a305a825913636fbafd0ba" test "digest computation - zero length payload": @@ -132,7 +132,7 @@ suite "Waku Message - Deterministic hashing": ## Then check: - messageHash.toHex() == + byteutils.toHex(messageHash) == "0f6448cc23b2db6c696aa6ab4b693eff4cf3549ff346fe1dbeb281697396a09f" test "waku message - check meta size is enforced": diff --git a/tests/waku_rln_relay/anvil_state/state-deployed-contracts-mint-and-approved.json.gz b/tests/waku_rln_relay/anvil_state/state-deployed-contracts-mint-and-approved.json.gz index ceb081c77788d7b5a3a933b2d0510303247694a1..b5fdebb741f2836e72a37eddc2e4cce6efe79b53 100644 GIT binary patch literal 118972 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%0DH@LYO;kGJ&VFipb80ePab@gq} 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

_gN@u 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 OUH9b49e}-tEfu9RH*{E#fa7nNxD+!ba zCAKF_lI^0z6e~LIFsC9kDv*^#o0(J~$0zdhZ3;o^&70ENaEt8vko z(2NG|N-+cFvFI(n)&j`f5eXAYg00}onO}ekhe^Zwrydl=3!||uX3W!! zdp4kTw({-sWajIaJY#K= z*(;ka9kfZKyN*l=XSK)ZFxVP6l9WO7C2Ihw*q9LQ1U$J}NK9bn=CIs_4rCqgk1Ujc z2c1ze06Rhi6=|BvQB{SO8I_W|=-&ZHR*VGnO)DU&MC6nuw1?>F@|UsL4s0332vG#F z6tUQWUU{Gl2w(5AJq1U$_@==&%+(~4F%U5tb>3h*Ha9{c@jd_!20uWl3ZkfLiZV=V zK)<$cpCbz{dxfUUlv_GF5QeREyVh6>MrNs80GR=s6J|7mT}$ARJiO=%<2_E#k+B3i z(|ZrF3fq{adq-wrRlt~4%`AtFEi3|XwDEK}>DDOi=)__g4+y_~jw};qNEqosN`)@8 z8ZsB#wf7QbExHvMz_v}rq;;jD#~eCVFD`Z@cs&6}?ot(@bxfcU$d^EKe9po<&yyD8 zqrznBhOJpLGuj&F{cKGb2Xf;<&iQ#VaD@Thr$dOu{6Im#hfuR=vUy=TBW~%;r7VDR z!jv;IJz$OKNTPV^eFC1WJw%9!4XXu>9djXpGfboz61@S22(<6|s2#MShQX$+QwjmI z0p;lwR%~Gt<1Koy^neF|0PT7#m23*gBj!5d_ADqY2{0spED)LGo|R4;K5}}V9Fvb0 z^9_j$gaHg0d5UYR#bk704EA*k9&Ei+fF(`SaN9PfZB5&@&1u`VZQIkfZTGZo+qP}} z`+2`}d2aS)Rqd>b%!;gtwPL7dEr)qehzhM&h?qfk;dnFxpod5VYBi{P$&65xD2ruv z;iv|IqE`Ip0*Lw#2dnl$RN@U#BbT(heR8T8EX!!;lx4ixDTVZ@=45o@lZWMue3mEb%jw}3` z^b!V<0ft&Q9;`%l#~|&Y_J)*>I0Qp-HFTy~LPoJ-P8{I0HkfQ0O~!Dt)^N$>(%ms0 z6m+(_ZJj*3rVCO7Q1mgFaW+=FQ{MndDws)$-KFv5Wk!>BvPwV__MXenq1Y1z(JWd#d z%6+W+5QK1YGCFAsLkZ)KM)h!?HmT1Q<0kKA(JD;r4Lj zWVFG2H%hm9sKEb&f>eA=7=_U=V^GvknH4O-o)1TUbTrPij*hTFd8YSovA_K_b*HN zfb}4`-3h_8-#p8~76V4Vhk_EVE?<2?V&SBowS5-ytE6SXObd^P#}S(Hgj+}ovE~cw z3mBiwVMiN{x@Z@RkdLt38+Nx;>qJilv4y1{R~AXp-l1{A<${v?#vhz(1uoH-a2CP; zp4Me{gHD7PhQ`3*&Qim#L!;m(eoKXl8$l*L(u--LcBo>Tu zY|$h3N_$TJ9%ZYOc;=f%S@@v-Zl*UezZJbxxOM&atDQVTS^m6yz?GHYdH9e8(> z5!@@l;ZgFisF@Q;Zp4CzT0|(Eo>VRHT!esdi0Y0MzPVJTuCubohmwMA5+D}Y@<*Os z0`TxRaG5u4Q*X$`(**>$7_lZnLg}%fW=_hsNZ8e(2asEX7U09DsV`7|?xN222*{;j zkv5Kt(p7|3S(%k!eHwOoKB6lHyIGjP5V4RYa!Nv{%k?ILk1~&FIs?;!n-0c1e=ZC$ zKGp0y{?f>6(@bh!cb@flmJb4^0se3Phm%ywmYGEWDcnPTt}vcYvkh-dlm2zXHXFcD zSoJS8jTz@_57*dgx;IgrBAUR%%FQAct2nyDr?8BDjew zr$2)xt8%q&Qi#R^xB3*}q4{`U;4oX)5ad?A4Qx-@7-pP8-~39X=Pu8Ct- z=UAgT!6B)5$k<=sWmzy~-}I{7!`Cib zn!h9z{CxV@3F@RziOV+LC zt2ATi{Er}H(wXFK1tr@^Df;pt#cyCKd?IqP0Y?qN!i++s)q=`ihq*OSbk-YsMs=44 z@c~xDI?1-oV;kt~ac-ZvBRTP!7h=st0Fxm^2r%qKm$6<*A3{ZqaNvBzdnEC`PWBy3 zH#Te{k>%qSzwV#4T45FjJ5^1X&#Mh$b=x@z$2?0?7nsFr)HHdh!J7F`Qj2zS8w4tH zFCru`285Izf)P&Eaj~XSDISYQ(Hu=E{z2A4XnY(YNE1nxjdE*DlnBo3I4;})Dq!fj zzUj2&Kb-TC621zoKmtXo9j!SwpH>hR5I5zG$ivB8D5@WcQX6zUzB&!2?ED9P4M>pE zdC>%YxC$B!S_)&*Pum$E`=)gUAgKktH5lYmE@@ zZUtn+Y=JOSl)qnt4X!T$MeV>n>Zah%vIm^`-QzOxQ-|mAEb_2j#yVcs$}-gJ{czd2 zNgC{ z-!8-fc2qT7KT`-Q&+lO(5`|y%$qq^B)y*2ns4BWk4_0=k0y06fYoKcMcQ5kI`TY=y zwOp4t86wq^8G{y$CaMS6Wr-Jq^?T1#*&R>sXbT7y15aEbWz`kD%d-u;yvN#e(Nt+;La{ zOKmShcMuAnff7(f_+O79#rbsnN}jGk%?+M&Cgz8tR&zujaNFxYOu$sjivG5Mzl5e_ zy$@5^M><+$#AiL3?BOo_N7M~Hi9SceJqj^El_p%bHbIq^!D$~5BlSnOe5OF*&1#V6 zj}^ip1Xx1F2lcysYqf?7Y?)_t`>F_X*;Co|}+#t%}U+7R$L-X};tDT`Y%ZjAHNS7)IZJOC!%ndn! z$b%(ZGMtq8tkj?d<>Drz`-k8`t=Zp#0MOJDZjWazL`EH; z7^YiR8-l#?ZR#(5H6bJZp*PT{>fj3@%kCa%G)RC4C7MrNgP{^4qayBCc{5qg0++LB z<0#~E@tg5!$!3@FQsf|sW3(~#b}r!jx7z|qP~eckBkRA@;PmD~N4}SVl)I1M+JQ&s z*W=`s&5h&JBnu$6 z^9^7Tl%bxc<`5Uu8AjP^Zs7WZ;QdciYbcP)_4>DgjRCaOW-D3do|9j^#5OZM3laqq zIeboy;$;nxOG0|*RkoNbutAxR)5AwDK}@Wr`BmoZ1=pHP{!qjiX@usKOC>>a*4c}K zgMtjo3t-M7GKl}0R}kKHw$@c}87mD(N0M|Y^IUY+yq?|DHXeR90KN8y&$L5d|jggu|h#K(F8d)Uv7o}i{-c~yqJxY)) zR5!1?m~+j+BKc!Pf?-~ldyYUkg8!$f9jB85R$0*xk%8Ig78s2b)eeB*J#FSx6*<*p zqSi!~cLBnJbs7@HFfM`RGz=ZaQZ(|dUOa?;Wz+y@Y7O^Uyts45JsR?WC^!96idZDg z>zMi?9XUDCgFzL570#LMjWsF*zadm0J>ex*=HDiQ%PePpZ!Bs_CEJeHr@{H~rK}n= za;*?K^gvdIP`M;|`M(C#{e|cOwK8DK6Tz8-`IYLJ{hy|`CE01%&}O`-M$Vxf5<3V% z?g=#&_B5cz2Y+GWJ*2BiH@&DyYss=`#Rr@e6ribPy}PD#Z@@L5A@xi(!Y)s6o07W8 zaHZsyiV2}nSJ)E!Ol)T8N)>@z))@Lae>2fL+VL7NGF!oLUH>unz;`;}jbh>;Gop%&?VkE?lt4A1 zBo}C=*Wp$h@;NmG#KuoTRp|dS)sAHJqI+q*nrEM3T4bx7$xbJVQlDBo<*k}({Aa4I z83UMVgXcobEqajxUDR&F4XEHC%PVP|kDLzMfbv%28T&YMRE@OLKGIw&XCX*_IhSXs z)ECBvpe-^im-+P5cWf*SM?ea z<>|9O+;uwPRTXM5ppZ=oi*{C_+tk1v%8?bM3N*)oQ0bi9uoM#Dr2tcHz5O;IXS&Q0 zJx>R16ZpeES*fKCT15d`OsHAHDe?cAYEfiWBhWPtS~8SwjmJLm?*3@FcuErV57#%0PN2&Prk zqDGL1mOw0okR|_w1jw#;Kzh&Ar#K7&=Ukq;3`Ak125}6cocBvl{ftWR)8Ev24joJ& zic?99981=qL*5)c|4g+t zj0~HIJbtmU&co3_@l7;w)&d^G0pnUF#Oa0+CVdq#?E*?XpmL$HIe9+}0tzN6h;Z{@ zdP@{~xM0D^i~K8do*_a&zXz2CWP+^Wngsk{;$TPdxKyF4$SM=&jL1CIY-)63q=sZ} zrkR#Cgz)d6QcnvlA}|64eIVahQ?^0p4DVfeWx1f{d>s)IIcRJ53gw!1rg@qb((91pev(=g>K8m=ICas+WJD z@^E6Lrv;$|4|1i2d@C+tTG>?kkERgmA)E_U`Omj3Jj1M#r1dJOeCUWo=?l<7qK^cK z%=f|)qk-%)UD(S*DM%Xrf%%1PMJiZHu|!&6WiVqPw;a24+@Sw9v7A`A;t-wRLea_c z=Y1H2dYhz@FjHwvkrUm^3|xxG1186saSYnAsYSUjCY=!TSNY*$Z?*NrqohQOD7wIm zJecKJTb-*rB2g?175T}Se)$;J4X$cS^J<;_bcLqMipDYt3n5ur@)5zrV$Kjhjh&y)05gkJ}{1l)b}G&vL264Z-*|98>3^_D{!n<6BAgsPqj zjoA^#Je(Fms>;puC*kb=8=+NsM_5WS>ZBP~QuNVakw3=svK|r`w`waUSS%TJQ06Sn zY4Ei^kUUu7t-$Ewr*(fIJSX)J(~5_|RR|lb>lmFbXhi>xNJ2M%D{Af`%>x2&7UIB= z6I$OJvNt+AeELB`z_f4POLiVD!E41B5!)CZ`Pj1j@3#mb4V?w&q5fo`gjXVSIlWWo zqNvFiMx&s~Ok^aP_#9g+Nx~$mC2|tr9P>qdHclX(339d)l@t(DrMKc?ijc{M(2;o2 zwMj$=0|)>K|EM7yl7BX@%t#xzi6<4^xhDUj zi8c$qZhjsOldn`^u%>w;W8U|s3KSDiEn&67>leNR2xx~_D0vFKIX6+UFyefGY z#rcxC&1~J{`-#OJF@-n~r?`e~?;;em4ZI^*dW!-BLM?DY!=WZVA~$Jx@OlXU3RKKd z8A2}3W}}AV=}n;|Fq?V!zKu|N2&9Y5lfoRgvHZ<+&s~Ix-3la(p?^zag<;hoZfJjlpoQ#8w zmhk`|X!bx^IAs8;+BJlQYg9T}$St8sO(x24vfU^;<1%>!l<*HgdPgFq&7fCZU&)w0 zgyTsDHCh<`lZRy3=2XI^UXR(uH5|xEIw`T@1(TDN6}P1dk1*7cEUj9bN#025PYJkZ z9;mxNe#SLZW8{WgZz3M5MseQVJhF*Y2~8*nA{uaT3}4GU%K@iw#DA_@M=C-{itnM^ z!JO!)fwW%tuQE&&+N4)YZxFT?cxhZ#3ShVz=?fXqtnkV7;r@J?Y8QkYxGOa6RU;~Z zs}`23sa{g_o$=gX^0L1)UYYH5m0|!PAxa0dJOmN!Md(tx9 zd*B}O%A#p>3|%-~q{2T3*S$=C2H>jYIjk(O#1KH>Wwggv-^oBt?ylO=StbWU!rls&POrV_c8?21lc9 zBJyrqL6WSxmpnLRv#cFmh*Gt zPyTTlPLxJV6hozs2RcwBMyhcG{qv*@jBPR5*K!ouNQJ!ElVr#VF01M+ILuBRTor^( z-4wWwEHni&d40aM z#mOJZ?bBB2>UPvRJ(Ev$Ypo~v0ytleqye?DY zV{HF^{KlDNK-p!N_fKRr9VIGNO`bqP+N>1DIif~mPn%>}DTc=4G|LK07zyx6a^B-T z#KJDiK5rbXOIP{UK3a{cs!&0FMis{tI%|gU|8%uN#A7ILZ?8?8E*%|CzBFxvjw{#K z8Fsqp&Z>(F z5V+b6B!?=TEyd(z=+zG@WjchS)STnGL=Sm6CC=!)M2 zgs^E-qx1*9XM6&cdAX@Kpxh^N9Om}@R4F^VdOBM8eFt{LV!kX>$j626)WS;XE258< zPWt6+`ZjqMtk=(9I(#v0COGbprb_#w-FjbOLkl-+T#JlxcPv3uyAeX&=Vo|O`JBVi z3h9hg&+gG<306~cf~5do?PoN8(!pylh-uHs*0>=}Ao$F&w&*>tq+H=0%`Ar*6pLBg z@rK{^6rvqp_uBx@of^C{z6@G~EPg0J_9gPG1vSsyWLnQ(sfkzw0c#Xboe$%3Za2S? zW5|^+#?@OI`ePFs=QJY{KAV9L$_Wm$ikF%NE(5F<7#UfJ*`Y8l<*6ppp0nWpx}iSa z>N~x{GXK|fc(p?CzsAGi zt)8AYP9JMP?bA&&vHuBA(?EaSC6aloVJ&&ZQ3L4kJ&hX3&M=M0fowig7-MNw-jb>o zryW1pSMAM&oQ-T=RWU~T4m0Y87N)FHH>erh z#DAb}4=nZWVdU{!i7I!;*Zs%BO3go0s?Yo77x~)Ren7H^w_f7kB(7C`8citIOg!cn zI4$R;5|wh7-BZ8{xBdP3Hg8H=`y62#Hu+&$x75d*XcTHHhWoT13B+`o4XFSP5>pzT zH=pD^9})bt5Z2@UejrBx7^vl{uWw=3$0~w{{uLeGTHX1S&WDv(V8O-Kt{&Q+&!##e zUC*D1j!tXpggw3aQvNQV7uSS5W2#%i<0HSX#q-1R!OBRjy{nGUuczx1hqXP+AlpEk ztJ+o4A7UTbgW=V7zb$vzK74h%Mg{EU>hh|XD|I>=Ht7R0T3S{M|1f2LM%;hgd2s)1 zAzvf{e7tuxGC6%Le=3{#TjPF`_-qc|Yg=>ust6YRigW3>;Xe5orV=K>=|S{D zj9FV04nQiMrc4L+d($Zo6-{t4n_hBgQO->Y{|(&B5!~}%9g9WJr9MY|(ADDYE{z%d ztSY|U;=hdlbTPzXe8U~z5%lO&^4TPA5(-2z&h`<~M6&3wPcC9HB;^z`>C@1N&V-la zYK(1&r0`DvVt~n7>wbG0JtJiN?(PvtzvKUTo0(r9<)4TOu1!){R=Rz*O3?)Qgy6=0j%|ix^*@v7RzfXyB(5n}q|IkS{ zTLSiVR`_GkZdi?3kBdEQ3l#CR^q@l1k6S3}&F$g9?sF)S^qNY_N5Y8fk)|3r_#NCA z_@ge|A2dYxB_3y#c~X{x&jY0B4SJWFOFoO8j?3TmGx=6)kA5O4L{>Q2JZD>B-onnF zRET14=Zex`Ib~}Z9i*71&LKK_juj{tGf`u>YihO=*7&By z&V$fUQVyYIm>L&WnE+A;RnOs%Ycd|H?h@r*RiGG{1f3xx0w%;_$t}JDp?`jdr*JfA z(o|dfwbAccJnCN@J#e(Lg<*>GFZg;`T}67##&u)37w_e56XX#ouDM0eMyf!?XH=6t zViSS(o$TbQr{m2F=5Rj?{WF|%n4Idu(Aq?`HlW&xO4V5LOJ`|iQsEvdES$B0bHJ@= zytSc69e4tzf=M%#y-FjMMeW9)j%W*+&hQTvTD1^*`IBQFtgoPD+dZi-#IS=gmF20E z&dvvvBNFXrD#q=Bp3q3Pk%~zmiqAL)*B-=qo|v@mPJjJd)9D z5Y$Gzgw<;-3AhQLJ2RFmThPX!?i`xeA$Pd1Yn^bIauz%-q-)jkPm-)pq=Z>}Y+=(7 z*DY8YTYdB6uBdYFEi{zOU1#f37+)K-;pKa|;w*=kHnwe9c&;ywC^59B8)($FJXxtk zi3d8d2VJzVE0~@u@w!x6rb^XwYAPAbv?D@nZ4BjfE0YUGOxN0%KugMB!ka)4|}k-9aU7oUDQ5Mso%P2Q3?vOI=#$09wMrZ zC<#@mS%EL+emQQqNrT`#I*H+(90#-=_*`JLFGl3l`CQZDq*jKy8YVAvuG=mPLJ!9X zF=KY=Pzo|182@p4sYuqcQ4`m4)PR(+rO^+0IOl@6N-(7u>a-jPx;gUcs=K&V}uc7nXaq?ce9qR6HC(=nN9VzQ5(Ve?1u(`Qw7y0D;@F~D71j1c+163tls6j zf_RSCE$VN{XUXMQQd1@+3Y|SH1(E&{51@tivZ@UCRE0|?5xQJjNYLLZ&PHrZvb?51 z90pD%W`OUsa%WmE1HkOA4|LZxbHe5&Nx&0`EP`!bGp^alUYkl$NqD@w?DTff_pTxi zn-PXMNJTG9zh%gOK&EciCEicX`19GC8Ag?>pPp)G85!rd!EIupWmyJ^1F`WdW%gDNaU1-ln)}jxR9`@RNTBUUj z>#;n}^uR4Ued4JscBl-yg@vIiHO(eLxL!?~RGCA&+iQ0*g9^sFdgKmw9{udXVl}Gb z=P=HruODql7#Zl8{BFSnN#xM;JQXHPP^Li?obq_(&4U?N!I>fQCgvjVcibMNOi@H3JI>FV6tG~yT=cGBf8 z@$K#*{w-0xe&^xx@wp4VsxrcQ|DMwQ`MGk)&ATLcSxoUO&L=6?{YC%dTla&e)!Utq zr^(NI(rtP2^g!zM5#Uh2_fAjhKdC(_<+#JD~N;ctlO;Ze-qk z>Ug+%$Pd1|IeMEvdiQQX+&!*{^1Eg<`ioghEuf}aroD!-+3c6u*tbbmH& z3GP%hiCL42QOsN7rkx|x`aY)}llIlMR3_eyGJ*F+r4q^|Ai{=NtT!PN7OPLx&`7); z!ly}O?*dEa&np_o$&{gM9?gTxf8U<_mC{2Z_>KIM^(^6W#36mw_m%7V(ev!`L+&CX zb!Pfg>sVbx97F0Q-jiYHc#gncqqfQM_VJE&6`FdIsijstU+nVZ-Rb)sPIp5ToCL$V zmqgbXp+W=mkJFD;9D%xIh5O>&*YB*Jo$Z4CYAUnZ`;w&=#mX}D+FJ#0o-Jhk{B}L6 zU=D6Z`!(%m(4qQ^l-+e8fe(+ZtlsEH&~4Z)9c7q~Il1qEmpGMEt$nKJIBV+XcwvG) zsieOB0z2#j;-_RN`%C9?)_iWA;^a#(gcfd6&@A1d^~Nk+^9}dO{@vd3Or_ups(p{A zua`zv9BDIuVEZ%1wOzLgEpmiNIQEz2t|V)2*7}sUqp>_ePi?Mtb5mbtgO(1!tF~6s zIJ~|Rbr7b%S23rng#Bsz+4|Jb?or!5(J>51bF;A7R!wK_?c;>-Ftx$$P+f>e>a6kc z>oCVNY^ppQ)K|CY{<*rg@wavReLT$5ZiT%(?D2Pp58Su+e= z`T&ULGm{mHg^btmP(q(h{;NfNPMWM_#tFUffdV(|h;6iy?l4`G6yCoIZ|& z=ATEvT4KsPdw6wly}h5$uwh&OTc)y)cOncs1+haNXP zj`F5+7@U*Hz^u1Zf+Y3%M)zzGeMN>*8Mvd40x)&PbU$H85fLpoY-w561n(f;O-)yp z>MI-V>d(~$nHl%?9cS72C;sj7N(~NWLyc;lDx91-qZ%al{$Wg02HYUGqK6pkp`n%f zP2NgZ(op|}TBY*#!&}1j-4gFep5CsL-?DfQuP^5#ShKxMo4rh<{p1+p%VvW1r1rm~ zemR2Uv|~oEOJ2$=pC&rEGR#=#nYfyjn?*_?r51eCCAAa2W6TzYCALh`dpz46*Z&cyB@aIxE zGqS3WQnG}UX?pWq#Y}fnXb4viKCZUow_U^zlmk1+O7GP6Y@}3H^aJm~-~fYT3-TGe z8c_9P&hR_&#k<#a{%qmREDRn2;LBoT^McORagSo_c48mCGxNXQ8FsXbhQk0X_WOC= z0zg-3U5GKUw%9A_A3n3|a8GD)Dl3UmtAOv1YyP^3E$p`u`E#lzjiUHq>d5n{Gj_zI zGt6p*ElC005Qx{MC!O?C6mdPgBJP|wk;0iMNKtnyhWsJ7g<&! zt~$U{Wm_q2BthiLi$G|S!_-kKZJa>Kl z;uNDvCXqbAGmTcQvz=E-juyg-lXUO5!B(;}>^sH9u&?U^tl53?s#m${N-f?4S_q9J z*jE;+qYImUY?D{l&Y-Px7O&k>>%0=<$HKAfoR25jT}IRd0E~WY0lzc#mfbr`N}=?= z$Is{cKtlQ0)!Nt83s%pM@h?kZG7<5Nx$Do-`4oQ@>pw_{!ZR0wu#_ov^(am}$7BC^ z+p61x$-pbswv~;DQip0VKg`wtm$lg0@n7HYYQIa|S(lc2fSJ|rQd z9~`%}o=mY|m5l#hJa#|jC#}&Fvw>k(c65p1+;+C)LD#?Ptv*#bby_(FjaDQ+yoBIH zqmi)ne7hzbcqjJUWZP-h=P)yUnk{`E99cieZmO%di5bd7qD{OE!_?{)4E%BI+-W%O zoE18Ga`xzI;cI=wgz|XD;1T~(yF&XtPxtzw&y1t~+9G<9gUb8?#fHwzbammfm;6HW z!1g-I)Ov9E?Xpdn!rT9(=Z>~Q{*a_uyo`Q6)xPia?D=KQRJ8s~aPu_rtr!GbIR@1I zU1c~!A4^V%L1!MiPx5v75$R!`XD1h43$v(VN+%b{ScJ}hPA!e~{DNGZEkd#UH+td|*yl>%Jzeou!oMbuC1rg06DZVi)Fa$y7h6~Fi! ze-U?AV-AXk_u^N_%kG5N_BI1g+|C06iY(1$_Vb2;xLH}2qtETf!5S1PNOiwn^Ke`) zWMj&SXppYLfBW6T`DYvtp&3UNg#zf(|6u>`M={cIZye3|rZNsW&r(I@!%5gd}2P}~ADYA|&f)fg?RG^57 z9e@8GD@a=O%e_%zWljUW1;5T2dnA!h-n;e}Of8k$=}uEY)U*j&d8F-?v-7a)ur5ga z<9T=~Fpu2L`CP8R>0)3`A*q3`^^Xm$68M~RxylOdyql4g3BI_=Qf3T6ge_JIRn7e* zz)Ux8aD`Yp8GYzPAy)LmJ8R$Wdl98t&&QK`ir*nfoFXg_FOutU8m5m1ypdl^@g7CI zu!N|O*DJx6MP`Jp!dzGA{;v36M&59A&_q=Rr8X&S-lnyu*?CJ#Cfkt026C5_7xXus zAU#`w^v-L_Sy$FT-#!Z!_aWK?K@!ZolwXN;Uhh*WUyB&EPTY}>Q3`6K;2{-s)o1Pn zNl}sv{mM?23dCqZI{JB>?j~qzTnz$2xDGGg017gqw~yx@qI*36Kq<_RALZd*{lXTV zWm`N+`#1UyU4jw2FA*MUMk_D0bUmv8I#xIDN%zjApsk*4(J1GdnOeWufnF=BV3p+B zrQu1vFTfV}`p$oSUk3)7*}{&}j(TZnTx&lXapMT~lDPz-?Mx_| zW{N0NZsRHF{@?s{s5)u+be%)q!d(mPV>-(#bmmy$;@dw}#+kTmD zn@5&=BCE9HS2Vx5XIdMWzm2A%4!N17z3Qv~5<#5kxllzNI^x3;EGoK0R)$pSjbuQK{ztqPAR<_A*tRFqvi*0i{_+6NnfKtyyO$Nqi18ZzW<-@Ly!bVy zC)RVbPm6=)2Pu<*^rqw*Ue(P!e!uSU?GUkif~(PJG?()`=ha$rLHbGkb2Ht|?MZ&EN` zH6@)bd(l!--U9i8Q|Mwz5)D_1sZ121QDpPm8Y?op9>SB)q;zg0OxqyZbRHCKmG6>x zbD(_g;ST%Upnz3>8ya6U6x0wxd{13x(Q|zq0LPB=MC;>H~#n2q5py$e<8KAuJtM8!~=GrXxl@vv%_e@ zV|DIW;R)Y=ymZ6=cKYTU)`mm;PWC=*556(-ci=L0E$4 zGUemF<8*=5S&O%w**Nm9nW(jz&ocGZOSUJLO$Tkt7vWBGd5;#EQqD4lU8d*@8#+jP zn0f>^2P;kpu53?;V?P8sHVuN1F8!oG1LI0bmrZTcW^4DhUdxv`It4JxW&=Yp_&JIS z#}8`vmSJKAY;TQP%-46LO=&m5u7R;c^+nY$w0Q@$$HrqWq|xe`Y;!K8E*O5lkTX5I z)ZyKyrC5lDRRdS_+3m&_`1}Lxs>GRW`a)GH#eJi(`{swcVe|1jwpQ_xc*UULp{%Atj+H{`zzFhRwFP#+teCn1nDjA+%krq&wXW}A12E}qubyJkV2r^#_ysbY&dDpnwIO<9JeWctp{1wwI- z(NrK7v{M0UPHD>vb=P`vgxmLbE0}`}Ow+?tIKbGS(e`EAf4INIxaHmenDp_UgOhD` zuE(wu(>wn2pzsA0hEpa+*$ag?pU=ZcQ=5^HW%~75sQRPjqlb3a17r3nAV%I!p1l=VJGa0e^ zbIZuPnYhz?jt{No1l zE>XiO&iKxy6Re6~LK}DGQ8HshDqpEf){fB*00pJQuF2VyN^Y*T!ioF-Gdw+PSBNae@-j-@`K@WshfTtW~wEoBuCrfPcBxzMd5 zPY4&Y{ne>_u+rV^Wx+dY*z64!-5ABD#tDd5MSp^14$J>+3?aH^g}lg$DuiCyDSG8RJ$ov)xK5ats3pmslmL#kZtSW${wC2 zk>x((l`5v^yJ{T!vMlxc`x!GaWt^&W+f4hq_(R<47vCFwU7>C(z=tMjVb-0T&I^;` zBlmHuij-YXHaw$F2l3d=0ej_$fhZo1|66Fp=f=yMfD?{g^~j5^8&SK*w6(F%ZWTY- z6MG~5f0XnbgU|WCTQp2Wu3M;K@whj7h8$>PjJC`s@tK8~!iV2qSTveFj$Pn=a^?qoZ?*GZkC&J-fntWzRn|QPtI{Z4{YXC?^uUT zZ$(BwxeSI@kZQPQ-H%~-+}PMN(F9r*wY5lHTc4?OWd}9Oa6i0A4YIr`|0Q7 zBhmWT6yg&6z#@tV~i&Gs(FbPu`MT)w$B_z(BsGta4u#evB?NB6m0U zU9J8Qm5r<<$34y5$@XE}BijlM4GxK&i`6nXJf1>68!vSzq@paBR)t}rGo`SDbh07*_ls>a% z-aWfg$~bEW+aj2U?!(RHW;)hPo@o_4TbDcWPyG^58!1=&#J32nRgUxD4n+&q9kQT+ z9r@f{X7aKU&&75J{>NQ$QbhVb&yf?n%x6|+nx(k;XvG6Sp=Zpe%_HjO92~6AOmv#D z6{6yn3U1lRd+tFtS5dBuPhzTSy4rNd$0dyMq9=&u^9;w4X(Se&P0H)3$&9zz4x+Hy z#B`D8eJsaxkhj67c45R_l19Ze0=5Uyz=~L`w%+K3kN#X9qjvHnfVul*||Qfw?j-r{oVlJJAPtTxRczmqeebL zWV$>1+p&cn#JgJ8=pS4jG2BCv+sw+Wb9X0AF4x;){6|b1YtEcGv5r|Wue_K>nBMEQ zvnX}(a-UhTPoBA9N{n%MF&|@xD@r5v0tqqOw||m-GSxXQ=(k4>P?Q;}9eDn?Qv((*zu4R`I|+wtHB>cI-3y}FcHf5+ z-y~`;-+dgRDpu+K+daQaK^Xw>>mDap9^K~7pidY&ln6!LP5F~-&D!}{!; zxXkxAyzThE+_lDF@KT1O{!%2Y zW?&!Q?nQ<34W{U*{^tg(2xFRy$bOuIm8lNnv+9T_XuFCvN@d0F@1c?g&J^fPjEP z%UEHFaOvmS84+4|sr(9PyGp2H>Spj4iCR~to-2 z*Ykakg{liXc;*Y~g4&qKQ5 z{%zu9z6K!_(dm8pyLS1z+~+HdWenkL*w6d(`{bSVOZ@$@B$e9lJ>h2$j^>KK*ykX5 z;sWILnxy8Zu=xInnUDIXh4ug*=)U>OkN<$8+eW~aE6OlBG9N>uD55+FG}*t}*}Por z4a{|;*OqvZM5*j-grX5qHGpCR(T^LMlPSuKIktqA!^e$I(1ac-vLl?)Ee@LGs1it$ zzIB$nqXr_hS*U#)4VPt!CiAPB3E?;bj8MoGO9}jv@MRT9b=2lHz~@-IvANyrESoI` z0(JSDu0{G_Ksb7Ux|+9x0#$`-XraiON)^MTfMp4kh*D4B)!#m`Vg|ay3Z%E8paZ*x z5h0l{E?l`_-W|VzG|2~=vO^lq)+U&0p+Rmq!#bFfPB=VZ`2VqW&%u>+?V`YA+n8u# z+fF9t#L13r8xz~MZQHgdwr%_7eNTPoo?GYswQ6^-)w@<#ckkY-p6B@i9Lk?(7jPM( zZ0G0cqn%pqpqvi4<>(%UUgf)=&sNE*yl$^OqPyvUZ%OtTrVW0`77;<~7~l)?5AxXC zfS*FZDz=dkPP$wOf&}Q(9vb@n)jpO~z*u%)pJ7^5V{UH7@{@Ntv}--m@ppT7`hrj& z+WNZk*8cXtr$ zd{ue9cUbF5gYo@{Cg}Fce!ti|%Knq3>!eT1Thpk3sqjOF)`HMiSKLH5#z6t6#T zo30Af6Y%i6MW=xkE)(0`l7@6bqb6Ho){?njex`-;I?nuLbp2MI>RF^lSW~gxpOe&? zerGrALUoI<5s+2rqyyGwM}LsuUiMG%{(bs!Hf;40L+z z?YfM!LE7mr9kR;i9&ju#9$Mq~a+B9Z|9rX_-;{X8kWT4oHKIAa@*3PB$L^=TM4sTFmn2Eh#>;mRii4>*@2Cgz&pVqL-^S2$d zhXSb|^>$_gWcQYft$_HH$%Vx{t>&x|X(Y;%nTm0E7^B zy}Fgl#j*GmLHrCca5U#u2Xf34E-{s z06}JSM0xf(MC{4VQovK#+SUH9`|;X0i;(Jg>e=&>JP~2|%_879V$Bg>3l`jiaKQmf z|3e}ifU_m5K?@Mkj2aF_Jj9jL&An}d{1U6Ah82~XCpt1qt|&`2Y8RR#8|(wIOqY@l zU#x@SG*NB@S&66V6S`oiS$Di={B?){xu87g@GMVK=n$@fX`98IoU*K`uYZMdHp5Ly zyi-}%_`7&$QtziU{bPNW7q6o+Zv9zr+Bjyz($#{<86jDby@h3QW_E38P)9pO` z=FHDsAg8VQtiL%2Ld2z%`!zwV7RS+(Q=Cz zB$~OS-%`U)`n2jTxr)sxD3vs^VZ+dU5;yj;HQZ4|4u-I8$zmB}5+61@U4<=`3+YcL=vc1pA5RX!S@k_vV3Xqu%;uvIF5WBH zy-RZwUldO_`gUP_=G&=YCCMg*P3ftziz%#1FAuc;IknPnu>D((##ZTHF3p;*^}~p& zQ$jP#?RQ$QS3?D_j_N`_(S~{O8enY>-6EZiW^VK}q5qT=<4q+3N7JK;7lVe)!PUjI zpaJN5<6Nq*u<}leDx{w&S)Y3Q@NNTVy3%T`V;-kDqLEEL&KbJM;VBcX5kV#0hhHWIs=F{ho>+Pq#lQ%aA zxJ~V}L&t2{E80k=4g9EujA_@qgmoy5#ygx}JanrTf?Flfc}sYP8ZUfU^U-OC_ml`N8cWZ2MyeqEInU)Yj?0Gg2d zkz@Pz(#iJNEXd?jw9IQMi7C6|UvR>^wG%)8Om1>Ww&I!gQjAj=ms|ApZOA){tPblj zDzseGJj-zB+&%6X9#J<5&@KWBJ~YPg+4E(8%$j(_N*U;Sz(KlxcxkT!m*Nh`ao-$`{?mEGGl2RGTi?kVjdP(Md6o@#Owu_I z?CAb1L#!fDgIokwQKRu8C{JU;r4yS930q=^E}Vm3dR}z(wSf3iK+KXj7+!8R)*@1K z(Ju>h4byy-NHxOATm~HHpa@}4G;H7GYQkBe#Z-=`wPyvJJ3VZttg=%6Kq12c4HFs3 zldTM5#dZWU$6U%IJ+e;644LWagF}RRiE?%I=L7q%aZOtK9HYw~Ci=73B$+R8 z{u68Rbho6E<(ur5J^p6cjG0WGW<4`+ZZUPlMwWWK`~cjvw6WsnDbosZog4J2 z`RlEn1cF$c;SQtU&zSBcN(fK4VJKt!}z9>-w({@V0}46>-E*^5-&ROY2FJKq==naz3+DcxR>jUPr}u? zY+|cqjVdr+vtTL%>=Qeq3Nwl-_+bHX;zb(j6Nc|>u)y_tGJyBnhKFTyHn-7>{lar> zNZYC3j0h}3-ek;~nM~c47nY24sw?*ORno6E$&VF&l%sz>_s1FlCr2>AwP=dkw#3Qm z=0_B*#>|}Bnj~@~uqGLML63vefV~$OAwb0ebCq?opNO{G0!&>t^~k3gh1M*b*0nz~ zVBEIdt$cq+MHzc)%7ap)!?u=**gI9MYe#(4xph>e1^p3GZ!o2h10~DWNAovy0!#Pk zXyk7CxC8ZhSZ0fwn$SBo)=(as$=#HejZ4lmm)_0HiS{X_e_1{LyPCuVWcc2lYG>u; zkD$VUmFJueoTsX)BDwee>7(r}^cAFHJ_g$adX!c=w*50w63i0`t2xDoPl&WDg5nqF zdic8My^yYb<}o*yLgbNr1p%$Ke7Zp@px+W>o9kq#oqr)*-D@*0PeKd&aBCds3;?x7u9RUP;A!(*UNX3qjPw34!-W*Lo<0*fGB` zN#zX!$q~GAa!tPSIBSS?&(p4wl$zz<_#BMD%`}qaK>5Vh34!@Y7ujaY_iGrZfnNQU zrp!6U6F8~FLH}pKg(JK#sQ3)*3>iRyY8HTNK{s3G=!C$1csxdP1PqPEF#}Siytt8c z+%WJ_4}{Z>O@?VuoBL~9psYw}I-g*{OapCR zbIMF z*VnUJRW%iR^uxK6fY)b=>R=(n9-43+iGvB3Jryc0UDF}bjyV61PE%{-+(s!2Vy5iP68API1#&0%FvtxOkr{swnJN(bZyUoQR=F=_A_!8&dMD|ID>Uw#PT01fN|&`kn;SDp419T z^Kk_kvwSH@G0;JMOayB!E2oaDw4xBtg(o@Z?D+*+8SswH!$*C@uo=oh68bPL%;9LP zzWt$m_r+giKZv#mVAMk?Uq4QNxKGWpefEm$LfWqq#q1$jLgq7Z0oled3Zi;&BJ$2T*elQH^G+GNA{wBeQuEWIPPoL=7I|nNjm*#hB{R zwL?_HCZfO}78)RvKyC01G;-rIzvu60ztGrs9QO8yf!kj#Uw%pi>`)4|2&ymNp^8y$ zn}{*$EA*(C<3}@wC5(*l z7$&X#U4oET!lO+@siNUCfykChbuo%W)S$nulz$K6fbI9V~t!9mJ)FYZR)$QYW*C_4{AeIaFjFPN`2rUC0v6;DpsVw4QXtPcc|la=Mt$-+JXp?LWO5JIFPQ%+57;_9*X)3)>Eb{a!7PywE&G{YlC zUTlEwn0e-^@a@%-5d`Z?uwA+*xB#dS@cI;cK1uB@Rut9p}AmZ4l%1G!XhngxEqSA(7}oE!j${sY$a+T}{8Wyatr z)t^3E-*2&{Q`xB2hpN*;Axv(L!i-|tYYuVEvJmg$@6dGeilv>^>YY{73=P#Qj@=z80#MEsI-Su$l7Uw5ixPMo}n1rge+1BT;^;82V+1C_f zgwuO!xGz{K3Iy+YeEheJt#ZDh=2gxo^?nq^+7<9-4%pUqIc?^?)$@z+Iry)3tgL`^ z1L0!>{?&TQcDRYb>7!*s(t{5@sklD_^;f(wmXQ8)R^@hN6O`{YGF9^@LZTs zv#3|uS1bH457q3BFqek${1^JGVJ~+(?VZtof;9T=9_=d3A07$TYP9}-^|+*Kb-quB zGxw~W((b{v3>ake)?h$XR?VN`6{NhM9Zj3;D8#j!8juac!?k7f@^VW`@RLiL2}Cwl znfz+uU$2fCpTmdgu8z!+J;Uw_8t2q@#57r}>eWhmN$bKXZ1iWibNoKYdI_*WEh{tk znwgT+{b>lZM`E!bw_88JRF+x5;Sp0lBp-a>`Qw$w6;&Gn*@+MM@>SGUI)mI#_OWju|C@6|tCVw;5pwKN?57RS8Q~S(8EGZ8E|tM~$vL zCBoVl&L`|r6s5_RzWHk#c=hOHwX}HS`T*><2o+MPRTYgpiE2AOJSK^|o?^kA>k{x+ znPRBY^yMtge}t!FT8;(MS33=3YaeuTbQ}k&W$7>^-NY)g%9D6;Pa>)V(`ogJem!d2 zmL1=m1gNayBI1QS>R=yZgy%2adG8{`l9+qP43Fs?GwkH}tr~F+>~iv*sP0?eM>H#k zZq>}S|K5=`CStEb07QZpL0=qmr+dw{N9(f!5895L!@HfkGHhtN$P*U zWi`&lLnY!NLGz|6^n6yA{+4yQL%G-0BUd{$G_jK}$04mGY%$oJ8;9np`791m4W6AC z^+Rl4+dS5BPL-tMT3eN5boIyC!1*;k{VnWF@o7E=^32P=-75k$zJ}D*qW6ewv1R-< zr1v?i34Ci5`;Wzsx=GzBfQlxdUuAX>eWq~MzjzjXPwNbty}C{;i=nhG2iwbbv_RCP zTQAl(NJ5_h{M@PhcvowxY~NgHYN#=9txdm}nUm1kkJZGRBn|csC#l+W2}G5V zM&gKeVFaec6P=POfu?}l`4Jt*(Hr?sSeuJ=;0XLj9Ys?PnKEpIAOVGI9f{!HH z>FWw=)Djk4l#>1ETGVQWpn<3YilyC0FT}4n?)&)%sn)Xqm-s%whWtIWH!n7bG+ars z7JfKVqADD#9JH$uW#wj5yZjg=GEX0S>@0=kFX@I=LCle`Dx2UR$es-6eO|JGp+z&U zay1WQO+P4hRmWE8VfXd*r=o>y6GmeNxW~^m_t;`D06Sz`{#p&jkkJX6$L9e{_lL&^D zx-pDMSlCIgO-vxm;TAVpN*J*MhPypSrGHm(sTW-3n;kwu*>jm>_k@na zv1#fyI7Y;seUz9s_uJK0Pp4-!4#WgBI_MVwAgeCC7`p+efm0L%!+4IP&^;J4#-p|v zLI)ScGzlmcH&4_P^Md?syb(Ofb>zIrDLRU0?uBK34ucaJm9Sw_{{Xx4oR`&*g-&lh zQuTXS<#su17N_3aIc!zcEwbwM?NNUCiJ`k##O!r&+v4oAI2KVz@-322jdpq-ibS$uYT6H_se=`# zt_1~kharNu%Bc~aQa!~kEE89@MJ{T)(U*V%b=lXE+5_&&OvNLPCJ)RQ|0QM*^>&xu znH8f>K>Jha*%W{bcjRXE8n1uaa=09WP%P8V6g&!^*5u+AyFX%$msb>Wq8|2;Hk*$* zZ#%>-8Gf~C(IUbiyV2U$0{zNmH`~OpH)+R-G*94Egczaz^+E-As60@c2HlRHX)P(} zY367i7I#zXLdB9|9^zU6yzFdsx0aMZN{x3tPOe1#%4l_%UgK6vMMwFgj`Iv%qfWO7 zLoEH=&p}1@oXcx|!p547ks|AnQ${P7C4<#_gWU;zbs~f5eSbB6Me*7TaIJ0^=wiH{ zveJ{NQ@(k)=BCizN{xYfUc+W&_idbfR z_{LhRo*RBl@ZfQV&JJ(q(K%E%=ToD7z-i#9QEJ~IqKbq7CBMFB;S+?o%BiQI^|!HE zX-RbVk+k)8tA^{#1UBbyQ3}24qRVFG+bI1z)QGJH0FTUgE1fs4!bvNR{O+e)) z^qFgAS81~q%}b1{1}uwqlPoyT&ruLX8eEbo2$+q0MJ<7H+8ZaKYYV4YeGUEIEngZ{ zok9`mp0%8FOdMb^20$V?4fe1U0dH`Uv(ZCO~3ry7#dzA@>Oa&D7Abcr36 zM1deDj7-gihF9uVnc&`1gAa(nlC%K(@B!Nq@jb26TQ}tlMbnBeCLSbk_8tw9*23mn(*oVY?s;j4^@M6D3m~>@S%YFWm$CPtg~0rSC_I7~SA5 zpC@O-2k_m@z+>BS9O$NhtfPa&K_=V%CT9IYjv(_Ltmt#i4@_^u>e4_C$W=0#26)A* zf#If^$zF$;#TSAh81bbQtb*$pepHBiXr}JiL?Mk>k%7|3)SF4nO{gMZ*eYT_xf=69cA%iUrjlF_DJ+Tc1d1zz!W4@Jt*E}yLgyX-ep9V#d%kz{8A#Fi zwjHds=7OO!G>UB?gvSLBwCw;a20yg@D_y@1)|G}LHuzoBPBk&8ZV+erVWh+RIVADp zpr2rnfbYH9fVJX-_Vl? zE>rjV;UjtCGQ{WIupHLSo&IE%L+zejUrAGT*Oyzz!^M%_%jfm*m5#Udv-az~S?)2r z<#Ay77vqPCHUB%<$-#Iuzt?*}q7#yO#V+6tjPn3DsR43wePySqGJF z&qJu4d8gM0On`>z}@q!Per|I9jfwC$+&KhqgQN zp|HY4DWqI=*qyoU)ud_TT2WT%UKU;)F@r58ru*od0!_X|K)HZ1{#jnQ|4%G;Q1qlS z-?$n>Ng8mjAJ$>hbHg+_3g|JqW$qYC>S8_h^w5I!6uFvxYkwK$>8>0|^V3}mFJ(Zu8{(5BCUNJMrtHCmw zJhI_Cep$05VwW}j>`V~K_7+5A#x$W;+g#SB1exlbsyFs!;D(Pd$oQ_60_?c9!|I%Ac53NiHZR$lZp;a ztx%KcGE${0G&SxcecPW`vl?mcQFYjx(R9OZnA323#SSX@>kGGQJlTAct9zYum>mj% z2L$9FYH0`o2lVye1;MQVS~=j*y^KJe9D_ymHE~G3+QCpq3!?>_*KoGks4d zoc_9$EJR`awMzB~po>R7EzYr=oUGuX*NEaCTi!Nn+3DPJ0pSMIG5x^kNMF zbYcB0Pd6CtVlfQGSe0XQ03%&}fzvphBea>yTsQ!%wbw_E&sC_`V{jd$+*HX~2xE}V ztQtjHtQirDdK|Q{@HZaa!7N!=>6`8SJYR(}K8=-fNH$^>wk&lrlQ+QNpuYZDz35`q zV!Fcvz}W+GrsL<@pNsoT?9tWtv`9vsFA~PrDE`|WUUb%cn0}8i$Rtu_(MNwUW)L#% zdqqqCPn}Hq7U1r7tpB z6KF;PlPY14Bx7)KL-%ch(+!Y}(cELgCXO873b9@B@voow6D|>Uv*ViL%Vp|5I>sNt z)9wa-qghSXF{|hy3~ur19<*{rMafOTH5>)H%Ik6Y$0; zz@Xjw30$(%y54PLtnG{#l^+&-#WG*OT_j6EAx#eyLn&?8N|tfr`CuFv z^CEFjK))@Rg4Ez=jDd5rxFz>@)r>F7pa>+;@oWEBf%Cy6=eFw&SZJ?5!;bcEH<>kh zeobac^gUO0`m;Z+GQeYLJfnK4ww>z-|4uzr6n|^6FIzKVOZ5>o%BW%#$;eWRKxkHb zGaP-EN>$8@z4q5U3z10`@;i7kQXWJ}BY=aobOukEmaXe_Qff?dMpgDjCn$&<6||u# zH<#Xo>7gKY?1c+at^0lDYJlmU_}~1b@jABZ^yr|&Z!~sbkrcti zPYP%QvZ2=+SerXcw@NWU2X@a{Ps$n0w5#HN0PdHP{Qc4{lY0VdV%BdhV>E7h=D#4_ ztPDEbQnoZ#7m{)rIJ4dN1KA7KSqZ)uESf2UlyZ)TaS*f##HRHSQY-dS(90Hr>Cz;~ z2HEK1Oyf@SZu+)G8oI`!rH-IjwLhY@o?MW*OfDAHCYYCHo6>NeYbKEIl!G1x8ZIgk zXRFB<6#ans23`gtFa;)R)YI5Giwg9`rjH6NM|ooo(Cy6oe|7FSpUnxaWm?sfL~nqEFUtL>^!lqgiVj$>5F^_BX$S5$ zJ}(^Qd=X?%Zz7V3Ka@Xms=YTPvWtAF>$u4{*YC<_mfb_#q-IBS<6WF?2gS2tH-%d= zL!)*f+~zx#vaskXXjGsBbI5FI!J=$%^Ueh#-%nBENoB#>*Y&7m3=ixG@8?O*Ov%wrPA+R2t!&owb|Fha>C|&SZb=}Bb24X_M4;i>D)%c7&!>_C;4DH?~bX* zpCLBQP8R_dzxz8`$x%JUcL8pv_R{nd3`yKNmml%EHuBJP@z(%#-PDCNcgxUA_P&8Z zW_{;jEO&R<yVQ@%W z%wsd?mdA*3Wh3pcb!@_hOv*r!z(%!8aHO56Hkj0{Hk#SqC=uCc@CU{%i;J9#(mGj3XhvZrW%ZVs@W-~_z!L3Lf=ftp2Lo}3Z0iGsr4Qed#h*LE3t=*l-$Y1*nq0=d zKBji(H9*p_J0!9h!)b76O>N!FWUq3Pr+;WolsSB9#TEL?WZ6{?$sKome~i0`KubEi zkt13VX}m-Ds+xT5GHsPR4XuM}(^Wl{OLUx~aTA~U8|epL?H!`Msvg_-K28U|1_3vh7ky#&roc84ku41=3$U1%{jLFY(~sV){ghW}L7VEi zm)ZMzy6b`e27vj5@6g!hL`^0yNL7-8eQ;weMlLsK@>964d3|w6L4eerN+Qrt;L_n` zJ=!wxPkOm~_qfFPxLekk!?B835aruCblU${lzl;Xr$Lr}J6%B-gKz8MjPA06l)5=Q ze7A&sIYzjDL0F}U4b3*;j(J3XEC#sbsFf`62k|ua zlBnLEMNZ-<(6)PYTsMZlwcm4v6tdjssG8hJ+23Q`hp!t9rtw{hHy(9~+6lH|OfKi; zJJ(A2oRynRiLA%-M7yt&v(9r*2kfS*@P-YHp9J%W($tN7XSi8XP8CO;943P+OpgT$ z`SVaqmWif%HASa7+7wtL+H~R%Dm-9MqNYbX= zO}ecBxW8!lIk_Yi_r*}P-(e44>sI97R_HhwfZ-ijb4xJpML$8GDm?T(J+pfOpRLR# zQQoJu@MZn=GrMUwfdC5~_NU*nea!DPQtS97{8eWm>^SzG(M*#K!40UQuj>&%IdZ#{ zw8y`~x?eFt(^{b3=;0vMe)81R{l;a1>yhT1len@HiGg|>@{Nwdh^%$Lb=lf@*B<n%huric?)N=W%}is6EW*tBd^eQq=~9n=n_8;`KNEq|}Gug?x1 z6MV<@j!$pVkBe^Xaa`GDJaJ5U{5ax`y2BrKi{A58Q25XqveRJp1e!2BrJLWhji2eI zNuajNcy(gMGsZvoPsfg7yUqX4`0veGdm%0V?j-ob`-Ap*-A~>If7q<74P!VIZ(irX zFOa7icrHmTg21OjrhJHRXWMZK6TJ?OrOX6;djV*nv1Va}9)B3ye^~dY+v27Kj2yaS68c^e`31A{d4-_ZCZyvI*Re4*rxu_-ReN< z04d+|yt^t*wWv#H8^HdSp15p;0bBY1L}k~;2vMd9g(Or zI3}yf?%(!%cXih#QNdgBfA``HTK{*nS9pwIApPbe}}$apZ#b2e;;=Ke{cGK zzv;WDP3m1YVZi-e@^qosb3O-HJXNs(-PdCYp2z$cX)CxVa@jF zJ#PSux;OINYYj5o?4-|T^m_ReE_<+ed$fYEk7i9AvUplT~ zak9zwm8kf#hE4Hxy;DrT#TO?KQQn9W+Ko3)^ZB|%0IlEAIo_;>a58=pf|*786fE;0 z&)ZWwSkKu+h`gNn3TtzPCp@LS#IVmBBCPjvHJi-&CB@4yx3F{u;t|-vwMMOdD6-c% z__HcRSjOTyMta3~%HL+$D=`O4~n+FlX3He~| zrF+t*yb(~{*bf6H-cn-^iKYc(&%mK|YDYGkj5^xZ9}XC`>QMS0*t_yewW`6xie*T( zn(T$G3ANbOgscKj)5xpIfPq{Q^HwAWDEt}tXj0c|#-Kjk87^Lo%v|~=3j0MZwbqIg zOw?fOS|bMwS-r_h$BXNj-PyyZ2Y9@z8$xT#t6fQKbswRj)Y80eC>& zB=gA}-AUqFyCx&uh9@RT1BvdoXEVJ-GG0LS2}5?Gy9Cn(cv_5|nM49l%CqRtLX%f9 zYKIC|<#{RY%F?|J)&q2d;#~C!mp2jj!VrV2ViIA!33vVi>h}BI7n028aV-hGjB4oi z`ynsC{e=SQ39Ct3;EOW~b`r1%FdyMK(&ZH*H``^@r4Q>;K_>i+b@s^;6Wfc^IyK>t zDPtX7(|5DS7E+4Eg;6pwfuIt$&FvgKZf((9;s zxY9J#fT4hWe-T<{S-%&w^B(V?DLG>fW5eZx1>4f^=kl4KdvfZY3J^+304!8JTXjecfd`R}xZdg6w5ix)&8DYIJh(TUqTgP#99 z!RwM{Zo@V>dvM35H@x+z;W&zssq=J`(0%)?dZ} zjxl7%>+?%0IEb>l`STyIBc<8ZtZlsxNsS05m#l!Wn2Jks4I|HEdg)Op?06o{m^yr z`|p;^b?cw@54eg8@CRKEzm7mR270MMglHFAZ11Syxv*Yr^8n$83)1BlVa3g8NPquX zB5=So!7kPwd$)su?Y{TN|I7}tC#{HWHCb>?EgL}w<8_Qd*72-~WG(tvJWsI68D4a@ zu^>_R<-k^i+5K5D%Y(gW>WPf54@LpNC>q$bVuUYC56x31=OU{fefUX4XGZay=a?@D zLBtVAxXD)$Yhli2JVH5S5~3)7ZVB}}G>!*oj_Bq4-(>Uc7!BeJ^)TyZP34wn+VJGF z_oIExRJzk1zM1{<57C~yC<}Pl)E^DKrNh*VVl&jk8NlAuU$`095nm^f-0mv)b6Dz3 zYL_qWfyR1Ylg4$;5vCqy*uDa~R>$>Az-Zx8<^H96TQac2I7?|4`YM{O3dvt(ti3Mh zQz?ha@fy2%Llcr?Nel8IZwN{u(rw^9RENi@VTI}EF^L$!sJFRe``t5&-?VtpGM^Aeg|6CnN+bf0Q-<5O-j4G{%e1)oHtXNTgGWzWj%WDKl`j)n;xd3y4 z2K{DUSQ9Dn((h!nW1_>UfuI!A7vIaBz%jf!L{-VuUYhxtmvDdMTj+FwwFVju*&TyrPH8Cd2EIw9a82~9v8%lBjBJ@lg*i!pp zOt4VU!qi?R%?eIbF|1-s8y>Zd?c)xuArgE-s`mTg`~A{1N`PpaoI8l)2P8+z+}c>g zk#ifhz&_9dxLB5wuTmtDa4FTd3hFpohnx2V1CF``@m>riwelN6L0$ln9RBzGj@O_u ztZo(zIgnO^U@^?L7OUtvejWL-I8fHxIg_LDaYbvuA|VYbjDa!?z_#K|9TtheK^2FC zbUBNzE&*=lUJ!(aM6R-2QBzHMbl1neSR&4Zs-`T2I;Ht;p(2UrD=jhp#)EHLbbSZH zdm3_m+m*-no~;F$>B`m+qcI@|41PETJr}mK$TZnjV|M>AJb)!H;t_Zt*KcIG#NJQC z|G}-#Qh-*VzKoPnkhRv@b#8M3-}97=6Q05Z5#dOYRe%~@_-xzP_X=FTy-gR}!h+tz z6P9KVKNLBor4dHM%r(B2Gemw_9<0X4mei5WDt6haGR9Sj1!O-zRI@X=o!76~|5*@- zI3(~g&W)6dMct4!k1F?pp(!M?Z zv7n&@+rw$`;|_|(5H)5J3cX@`(-q2YjFJI~C)O}&^y(1`9}oeq#^W3wej+p05=te3 zn!2*)UZqQp69Mhm=c~OBHRhWuX{P~if%P;JMngS?s<@tFFnDpZbX&US#kFJLhM6pP zaj96p4)P@0OxQ6?%RqgvHYcMlgA2Tf(stEb3KDRF{{`tiqOi@aLNYRo{TIvx?o~`> zxBR!1Rg@j^MT7HQuld`G-Gj_#N6AYiWAU_ca8p9P?kDzd%J5t@>GI!s# z*&fnffAviXx`rv6qvurs(&jNn#uPGcbkkGba(t-(LLkOK7m#wjNW9G`3mkASK}LBq zie0O^DJk}rG&s{(pg!*A4EWL4+BFEYo4O%kyJ{WDVx0v`qhOp5N@*>YZ4lUbJ}$jJ z#R=?y5|jqr;t)#Ul@BIRY&mpJD7N^D*I>Zn(u|KiQhEFD-@@KOrwEj}OfDP&Xn-IT zki_9=z;*f&c%7(;l}p8KSTo@_FJ41pg$jFZh$9c|`Klm}t}n5=pLW7PjWx&@kNF;S z(yCj>dLO@;5AH4yT^k4P39F|2&5Oer9wN94u!=x)hkfGw0B^`;D&y+_b-(NYabLl= z&2oei)SBTiH~qs3oUVq<6l;~}lP_+Wt&me>0(mP(e&&&L{ip=x#6m72rVgCcqdAXSWcW~k zG0T1x8goGhh5wKNEzqP^)XcyZs0;?@K9~=R9`&*PA*lQg5KD&lsbFkp%~vq^0MDd& zR)rYmo^<7b){G%`uCM4Usi?1gAmlTa!w2lez)Tg%j6_Qgsv(L1YOwEk_;?vp&NFw; zD2^(lN6F6xl@sRh2bBwZRKkIl*s>HQ_g)bjJVG#AGgGuy6Hv_~if*s82y#4xl4Y_L z+NzT|-*lAc65{k2jf+}t%O;3R5~FR;Q5-)dOUHw5(6n_L4l={TL)-Nk4v~`-elp@5 zWgufoh><8=L6Ni~dnIK(!$rXnq1yr)i{g&lJA7$0-`lP zYYId95qMR0ky5H%R=QxrEN=>WS-*+#JVp|IxYLeaCrfLztzl0j4mE2`No6DvWyJ(N z;!MI#P!SBCM;Mu88GI*40XTsAvEm+$8m#64NG!I#1IRDpk;VWXkUH?6+E^CPmy}FV z0w(m_LAqiP5PnpmiX|X@6;!xN9GZH_gkreUlKdMug?LiXsYBMt-CkidsDvaD_X4C) z;hME*EmfV#6c)3UBS3XlR6dJ|VVkuyB0=v^V{Bux?}wE3$z@QL1|U&B-C${Z4j7wm z?ivx%Fg<_aRqG8YBiPPaHqsanjzz_$-ik+KsNwd#zKa?8AbK)QizT za=wvqEw$P}nTWH~ULhAuBtoBdd@<5=zkOr}yu@8v1{!E25-4>6rcrL{Ot$y2_To{- z7gJyxTTOAS7$V}%mL?6Ld{_h9YQQy?60j;Wp94P6m@}TeQ&lHqT7^J>K1$D01^U1@ zGoJobD60WSGXN33gp?1Ot`LLr23HZyF{Yh(dgV(-eU4);#e(j0MJB=^4SwD~)MM8U z7L4pehPDpI&@zUksks*5q`Q@GvCLgO>SI)mGUC(gp@Po>Th>Wm2wrG=IJaj~iu6C3 zab!l0J>KsG@Y;Qdt}q89W1naQOY)^T|Gxr^i-Im}Lb`{Pj&Y_?@{&gwz)CN4rc7Ra zeGq$$Sk%ZZ+9-6ou@S?KrEdM#ut230NW^5<6sj7KBaSjEUkIl5{&NVm$i*>sP@~S8 zCb4yt5IyH(q``rlih|XN)4m!p&1`FunB^2#K4i^=urddt{#`7=A_A6REm(gSS(5TC z+3?tvEX0$17fXR^B^maG8B$Jp<@e1Pl1P`1Wd>NS%ZgWl7RU&Zs5>A7F~r3#6MP{k zoqM$J5b-woMU45aY=4KS<}!)(7^>j1`$$Ow1!;I(pyi$<%I!Lo3a~DZyl04o338PGzfWl$pal%i5izl6Wl_jDI!Bfe5 zTw@;FBNahKGv6e!r(q#}yEA)l+&LpXhST%xcw=0su)vXNfU?C|KZMOVxG6S+%Bq3C z`LqJlQFl%9@ngp*cj?8We%Vca)O}EMSQ`2rMRcVMvdcbRI2mxVS(}|8O#meudCXt> zJ=U*yYIJCK6-@gf#^4R@P?2In>!wHuj8UvGuhFm@CFV+c*%&d^zv?bR)>S&bufUq* zKKv2ODp+XjbfBO)jA24)HaKgZ8Wa*NM=&jj9=vNp(^HY!?`t*U?xyQxTM2x!m+QI_ zs0juhe;+dioGKv#r6oJ2K*le^sD4COR>qB&q{Kxwo^&cM(~Z~>h_E;>A+8jBpI*eFo%t=!iEJ$ zLHZZ+Di(@Br|W}pCTLV{)>DqRm}DS|OJ&JSA#($;P%q0IxA`~Zr}UpRCaiKmf%EzV zC7tI4r5FMPn+|zr_G4l8ydX%+fTasPa4m{k8!dRMG$f%%{KS0z0mp6s3mor)wWzbL z+U={m>MXEp(C4hsjR^;ha}f=N4hbG3WgtKFpvor{wjgJUbou_-sEF$o4F}ycdM!&d z#3}lD39R>j!SNdu82`u7QZV^%aQyqp0?OL!OxmSIxUJe>gF>U2wl4+RVs&T!e5>h# z9&T~I25*I5bG(r1A{cKHFTDVaIsB7}Y4_NI@RWgv4CL}W#b@oTtx+H*FU`7lFknxH zvC|`MUBd+|W^B8z3QW9L-jgTRzbFh$oa(4Nkk3XTghd~CVo=zh!pjxz&mEdNOVa9` z(qFv!G9eXNp%(Eh^;&ofq(FNTzJ4}pidY#^Gk{=uUiItu-5T-_IDQa*swE%hHZi0= z60|I4ibD;a0tbXnq{gVQARron)oGMJD zeS71X&rbz`=6s+~Xe8haXnT#i%3)LPDm~)o<9Zi89+ZiHO zA2`|}IuZw1j?V}uH=a0{M7cOINRHu3R8~4HhU!s(ggpk|Lj3(h_9>wSC4-P+3kl~J z;yPzF8y&TU<3NB4z|GkH5yy*$2{AR8w$4R;NgyCKsu)MtO1D#k4d-Wk#yC&|Hmlj8z@3Th>w#0phpgd_}F zH9?xQZl)dI?;&tSID`bE>%yc|AxX#ZeZOo2!I@cqh5jGL?lC&@u3HdzY}>YN+qRu_ ztcuyOla6iMw#|ys9dw*@I`-swXXc%md+(Z=Z}q9_ziQRlwf8x{v-cGmhDhYs%yETt z{njN){NS+Ph8BNd$s6C{3NnI-YiM8v%5QRt`oQ{Vt7|FBgY`}*ZFrE%Kx%N{QG7l* zboG*hY!lH=o?Xz=Tc~9w-Hn^^*lE&kJa*GbkiA!@r=up5VHNa5f^5J8!yVF|>S zy0%dOVl9mH)(?^%V0UB}N^VkikjCJ$9C+F~w2X2i)IK60zA8gG%jIX+ui8n z+-6gv)GoB*RUKzIHCs9knk!uz>|YhjiaoAmcBXPfcO-8Tyk`0WY_ZAfMC_w$QY0pJ zVLiJ@6^^kDvSdfd6q$6mS|kj$sWH3*LG|bP#4dx1DbZ3A`#s52gvu7rg;U)NK6rQ- z@!oMZu%{Y`bzD{SjZ>gcGzO>2BLGVeO#4QtW>O9>KT0Od2$ZHeW3oyB&RAVoISDX| zV8rH4avAoloUSf-N8D0$go2GXw=~Vg$8^wYKytMrP5hL7Rfm3fmLE zQ^Ced-2V!^W=%@=)>|6|J1a<(Uz3$~z`3g@A!TIjD|xxQzz|^!8U}zA4OI6;Hf&O+ zX=Y77vjo?3s>X?{nnOKILBiRm^wTSmaZdaanSYEl7Z@Y~K=y;+2TE$M7pz$dLh1JO0PvAD5ckqd^xQ(ZaGwf7(u|2=kGk`{oH4iQ9TC8*8qGn4vbw~@y19FTK!w9N)6?M(GK_upi zU~M=2fBwK`gOx<%=xQ@i=HfQWT~p3&T9xNw)(D(3jt%RPaz9e(dShDC266wBQl+hr z@XtV{izFj?t!Gq^9U*XH?g&mO57v$l2tnvI4*2xS>9r0_Mg5bo(?>k(fRd~l5jHGW zBVh(;z@)}2CF9|+4F5Gtp=3+(uo*)91m)iUNFn3g@TP$37d+iw__FbiiNzqoRNrKcy=+?HHD1< z&D&4O8YJab-;<=su}=jS1R0y;)%1`DX)ZXCidO3I#e=4=IXg0HqL~aD9VJ!+E1SvA zzaC&oEYX(L3hM)Sp$MN#7zAGzvKO7^TpLox99sIJ%dP~Ou&Db60iE*oy{4&n2Mzyh z+zE!OMk~4;Ao^pF^GdQdWG+tr0O3{h{Z!0O!3gra`zxr#a_Z=UXo4#Or5jfC(BE5- zP`%uso)nlP`wegbI)y}@c^7z8AFba9G0exo=5zRO5L{+4(km%lQcnAAxK?FbSDdBOpAX7c{CO<~toFxDzkeVr8-iji!FQF}3{ z275Pf{`m6%JWcGy@-ULdzP%uOl?z%XI39N-HbaipM3(SQMi*Zvc)9ILj_Rc+Fj`@U ztCmti|kDn4vq0}suAOx8$Y!Ck%{`!Km%b0c*t{1EGp5Cf|2ro zhN3|vW;8NiqsKwWSxzeko%xvRE4mInlNiZ3saXeY!6(-_ z6~YY?3Pb%GJ-)0B{~mCiM+APn4vsRUQUBe8&bgF`a)gRd-@Qm5OJ~f1iy=wsLJp1| zHSHYfx=5(}?t-dR!m807O9KhAD7hdeV5N3taT)!G!FMYajE7~mcrIFy))|KHdwiT@ z(d%22dew#zW$3*qpnt_o&!_ULJZDI>e=;h}*BFrLhjE6We@(~S3l6O@n}S^`fbQcO zkB2do*OHV6t3r|(8`5)GfYdz|i_aA$JGohmp9GUa?wyFHsYX*YFr*5+7plSz_svpFqt!ae;Vh0YgrFqG zjP34O$1HjD!~lByT~LQnqq2Ahd>uI>j^hcJLaAI-t_8JNWM8e09Vh$OL>rnFq*(-8 z<+KSsXrT71<Zp)3+*tmak0qSfm^2Ni|lsp?{@D1$AGH`=6c`soTYo9?b|*-EZtH}0M)DPhl_ zr(fK;R%$L2s1*dmd-a-QqVO8T&|x9)`@37wy3I;50!%$pdmgZxiP9=e6iS)I@J@Y% z(J~>$c4fvm2{IzGH_o+Bt?K;VR&j)Zq*$}Kt`ZnfMIw;Hl$U&jT>2Uelg-ICu5cMB}3P9^o(>cQgA2x;d z@0CZZ`yrvmzlF1Mtlfg@Ta+XlaC&lA=Y+b>fuyuWw|LQ=kJJIx(GJrI;)KxKI{#a5$zqS#VhH}XZZQmWr30qU>BDQ)(%H( z%eTPPt0OMnC##0t1)X}^)}o%w-#0XdQyDuMn$SF?71waYv_(TUUOO6tQ`bK`Nebql zG8VHOt242`8V9_BB1({*_&4co&Iz>hRj{sQc9T+u8yOD?wrFq=9^@7>u+~-C!A#?N z4!2i40vrS+Yjn>FJ5nA_Fq!WqPd-7du}w>t)%k4koRp=vn+JN68t69^98I+?3So3} za`6%6iYYneI5_8gLlon&BV40-61iD=BegxNq1TllRamNRkQ8dX_Ng_g3~X*Bw3C0P zDS3w}#_wN)B%l)i)J7CM}#khU6eWrm$-Rn5?rcM+gqR>igvLa^p4Jw;JvY}-?NSyY*5ev-H;|K-Y zL`?ietDJuLy@1{OI2!C^#2{r6Yn*&? z&xW%zAWWyCAb$xi%}|_w*TPH$S`P7MmtIZ`0xB0t zD#;8LeeqeBKQftKzY4`j477-mcu9jP?n^`#SLk6i8kN~4*9!X%1bDdt+4`cF%

Y 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[vhcB}$QAdK7h~uvNm7smeS3``zD_;*p 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 VmB|RDAOku$xApL9e~9Hv{{jM|=LrA+ literal 118346 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 z z!PQ=H9UMRgPp^bg#6+nM$#uYewvURinIg7E=RpjI?O)h4Z}0_C+|PHQ+NG=76{ip{ILSg)coeov+bz?ZkOg^7Y< z1EUvFDxQ?hOAs2``nc@2UU4s(V!N2izLx4-lsa7zljyic$+vhqRt58H`cl2H536oK z^g{G^(mQfRjyjs{8Nvmp>2f)Ho1zKP(O6s=i@%cX-t%rt1+)YERuYPvDqjA7;0x5v zsQ;Mvf4dKOD*E%^?}Pf0UuYn>P?P94^xe%>SMYpa!qXoui^BY2e^qI!`nrUB>KdYI zBi1IGPS30N=U4S^WUFWJ8AvnoLH+*Q*GRhk{yZHx)|rnNHbYkw@*jQuI+vp3nDWf3 zJhRe>pE*@otqYaJK+{``ii+VZE>h$2LK{vKKDT{PzG8nb@N+O=QT zY_ejX8HV`z32rNfJb%*+o$s}xGJLc+nyeVggt(gIRYyIDCOeL#&oxndwz@(uUAH}7 zPnD$#Rg8t%02A?Vsp1Z|bzLlo=k}mfkfP=lsh|-vsxx6Po?FlDNdIH#BFD_C-CAO5 zQ^8wnO1rNa<2X(saMzK|l0gCcfr!9Ccc{I;A_NT24yq6fX|a|1vWK-p2e%B?m}Kc8 zhaOaPj`>J^Vw56y_$b-KF=FUguA&&jPGJ{Ra)<3!;rbb9a$pcgt&qk+#!r?!-mxm% zrHp5ND~tN+rgSbrIlyCpOMo2ewTfsC!J0fk4!bAyZdl!x_%x92%|jWEw`Y-iwO~J< zhE_jbnUBr#PCxnU_LINX_v2sub<;a|U;ktLwdO^{jWA?<@eBlAtoQ_Xed_I4)TX3? zib{EoVv#@)PhmXK>29A6N}sXTUq|*wc-d!UrE5A@U2GWyW4ieJEuz(;QqQy)uMaVX zw-oL@R*Y?fmBmu1t1*6-Q57AK{sDfmeMa?E%cHjGY!si*K026*;uYC(?@sW3O^jcFwtzsPz>! z%PALS79p0-Y}uH-82F9wEf$vGG$$Izon#yncA{Y~*2HqSh%Q?Gy?MXi6xS;ZOEoKI zV=QXvJX!+w;vx7C{{U#=JvG=r$vd;dT5=GGrlbXJ;h-)55av`niKjas^E|e%yCsBx zlAy?$2QQVgWB=qz+P3rtr?Iloe2ekcCazcAm>hTCD?!4-d7q&6_F{D_Jl8#Iu(=C?np-=z$N&9|SIJ%z zWp7H9*!QSyF{YAu_O8re@C=UjB?9`vqhecxsUqeX`;?EtJw@eq`d83=BypT|qHCtBUYf zXI-%hV|li@CvOUir~E*-Q+2T|E;5Hv#CA4=dT~#k=Vg_?IG6|f&8An^xgY?TnL=0J zY7v_}Vb7H!EmoDzdh$lWh0Ice{I*|R(v6@r&c3>dLKb(mQctsD{zc⁡~sqblGgo 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 TCAs$Wd2js-fauz)YDEbEVqEpL diff --git a/tests/waku_store/test_wakunode_store.nim b/tests/waku_store/test_wakunode_store.nim index 7d1a44ecc..e30854906 100644 --- a/tests/waku_store/test_wakunode_store.nim +++ b/tests/waku_store/test_wakunode_store.nim @@ -386,7 +386,7 @@ procSuite "WakuNode - Store": let mountArchiveRes = server.mountArchive(archiveA) assert mountArchiveRes.isOk(), mountArchiveRes.error - waitFor server.mountStore((3, 500.millis)) + waitFor server.mountStore((3, 200.millis)) client.mountStoreClient() @@ -413,11 +413,11 @@ procSuite "WakuNode - Store": for count in 0 ..< 3: waitFor successProc() - waitFor sleepAsync(5.millis) + waitFor sleepAsync(1.millis) waitFor failsProc() - waitFor sleepAsync(500.millis) + waitFor sleepAsync(200.millis) for count in 0 ..< 3: waitFor successProc() diff --git a/vendor/nim-dnsdisc b/vendor/nim-dnsdisc index b71d029f4..203abd2b3 160000 --- a/vendor/nim-dnsdisc +++ b/vendor/nim-dnsdisc @@ -1 +1 @@ -Subproject commit b71d029f4da4ec56974d54c04518bada00e1b623 +Subproject commit 203abd2b3e758e0ea3ae325769b20a7e1bcd1010 diff --git a/vendor/nim-faststreams b/vendor/nim-faststreams index c3ac3f639..ce27581a3 160000 --- a/vendor/nim-faststreams +++ b/vendor/nim-faststreams @@ -1 +1 @@ -Subproject commit c3ac3f639ed1d62f59d3077d376a29c63ac9750c +Subproject commit ce27581a3e881f782f482cb66dc5b07a02bd615e diff --git a/vendor/nim-http-utils b/vendor/nim-http-utils index 79cbab146..c53852d9e 160000 --- a/vendor/nim-http-utils +++ b/vendor/nim-http-utils @@ -1 +1 @@ -Subproject commit 79cbab1460f4c0cdde2084589d017c43a3d7b4f1 +Subproject commit c53852d9e24205b6363bba517fa8ee7bde823691 diff --git a/vendor/nim-json-serialization b/vendor/nim-json-serialization index b65fd6a7e..c343b0e24 160000 --- a/vendor/nim-json-serialization +++ b/vendor/nim-json-serialization @@ -1 +1 @@ -Subproject commit b65fd6a7e64c864dabe40e7dfd6c7d07db0014ac +Subproject commit c343b0e243d9e17e2c40f3a8a24340f7c4a71d44 diff --git a/vendor/nim-libp2p b/vendor/nim-libp2p index eb7e6ff89..ca48c3718 160000 --- a/vendor/nim-libp2p +++ b/vendor/nim-libp2p @@ -1 +1 @@ -Subproject commit eb7e6ff89889e41b57515f891ba82986c54809fb +Subproject commit ca48c3718246bb411ff0e354a70cb82d9a28de0d diff --git a/vendor/nim-lsquic b/vendor/nim-lsquic index f3fe33462..4fb03ee7b 160000 --- a/vendor/nim-lsquic +++ b/vendor/nim-lsquic @@ -1 +1 @@ -Subproject commit f3fe33462601ea34eb2e8e9c357c92e61f8d121b +Subproject commit 4fb03ee7bfb39aecb3316889fdcb60bec3d0936f diff --git a/vendor/nim-metrics b/vendor/nim-metrics index ecf64c607..11d0cddfb 160000 --- a/vendor/nim-metrics +++ b/vendor/nim-metrics @@ -1 +1 @@ -Subproject commit ecf64c6078d1276d3b7d9b3d931fbdb70004db11 +Subproject commit 11d0cddfb0e711aa2a8c75d1892ae24a64c299fc diff --git a/vendor/nim-presto b/vendor/nim-presto index 92b1c7ff1..d66043dd7 160000 --- a/vendor/nim-presto +++ b/vendor/nim-presto @@ -1 +1 @@ -Subproject commit 92b1c7ff141e6920e1f8a98a14c35c1fa098e3be +Subproject commit d66043dd7ede146442e6c39720c76a20bde5225f diff --git a/vendor/nim-serialization b/vendor/nim-serialization index 6f525d544..b0f2fa329 160000 --- a/vendor/nim-serialization +++ b/vendor/nim-serialization @@ -1 +1 @@ -Subproject commit 6f525d5447d97256750ca7856faead03e562ed20 +Subproject commit b0f2fa32960ea532a184394b0f27be37bd80248b diff --git a/vendor/nim-sqlite3-abi b/vendor/nim-sqlite3-abi index bdf01cf42..89ba51f55 160000 --- a/vendor/nim-sqlite3-abi +++ b/vendor/nim-sqlite3-abi @@ -1 +1 @@ -Subproject commit bdf01cf4236fb40788f0733466cdf6708783cbac +Subproject commit 89ba51f557414d3a3e17ab3df8270e1bdaa3ca2a diff --git a/vendor/nim-stew b/vendor/nim-stew index e57400149..b66168735 160000 --- a/vendor/nim-stew +++ b/vendor/nim-stew @@ -1 +1 @@ -Subproject commit e5740014961438610d336cd81706582dbf2c96f0 +Subproject commit b66168735d6f3841c5239c3169d3fe5fe98b1257 diff --git a/vendor/nim-testutils b/vendor/nim-testutils index 94d68e796..e4d37dc16 160000 --- a/vendor/nim-testutils +++ b/vendor/nim-testutils @@ -1 +1 @@ -Subproject commit 94d68e796c045d5b37cabc6be32d7bfa168f8857 +Subproject commit e4d37dc1652d5c63afb89907efb5a5e812261797 diff --git a/vendor/nim-toml-serialization b/vendor/nim-toml-serialization index fea85b27f..b5b387e6f 160000 --- a/vendor/nim-toml-serialization +++ b/vendor/nim-toml-serialization @@ -1 +1 @@ -Subproject commit fea85b27f0badcf617033ca1bc05444b5fd8aa7a +Subproject commit b5b387e6fb2a7cc75d54a269b07cc6218361bd46 diff --git a/vendor/nim-unittest2 b/vendor/nim-unittest2 index 8b51e99b4..26f2ef3ae 160000 --- a/vendor/nim-unittest2 +++ b/vendor/nim-unittest2 @@ -1 +1 @@ -Subproject commit 8b51e99b4a57fcfb31689230e75595f024543024 +Subproject commit 26f2ef3ae0ec72a2a75bfe557e02e88f6a31c189 diff --git a/vendor/nim-websock b/vendor/nim-websock index ebe308a79..35ae76f15 160000 --- a/vendor/nim-websock +++ b/vendor/nim-websock @@ -1 +1 @@ -Subproject commit ebe308a79a7b440a11dfbe74f352be86a3883508 +Subproject commit 35ae76f1559e835c80f9c1a3943bf995d3dd9eb5 diff --git a/vendor/waku-rlnv2-contract b/vendor/waku-rlnv2-contract index 8a338f354..d9906ef40 160000 --- a/vendor/waku-rlnv2-contract +++ b/vendor/waku-rlnv2-contract @@ -1 +1 @@ -Subproject commit 8a338f354481e8a3f3d64a72e38fad4c62e32dcd +Subproject commit d9906ef40f1e113fcf51de4ad27c61aa45375c2d diff --git a/vendor/zerokit b/vendor/zerokit index 70c79fbc9..a4bb3feb5 160000 --- a/vendor/zerokit +++ b/vendor/zerokit @@ -1 +1 @@ -Subproject commit 70c79fbc989d4f87d9352b2f4bddcb60ebe55b19 +Subproject commit a4bb3feb5054e6fd24827adf204493e6e173437b diff --git a/waku/node/delivery_service/send_service/relay_processor.nim b/waku/node/delivery_service/send_service/relay_processor.nim index 94cb63776..974c22f6c 100644 --- a/waku/node/delivery_service/send_service/relay_processor.nim +++ b/waku/node/delivery_service/send_service/relay_processor.nim @@ -70,7 +70,9 @@ method sendImpl*(self: RelaySendProcessor, task: DeliveryTask) {.async.} = if noOfPublishedPeers > 0: info "Message propagated via Relay", - requestId = task.requestId, msgHash = task.msgHash.to0xHex(), noOfPeers = noOfPublishedPeers + requestId = task.requestId, + msgHash = task.msgHash.to0xHex(), + noOfPeers = noOfPublishedPeers task.state = DeliveryState.SuccessfullyPropagated task.deliveryTime = Moment.now() else: diff --git a/waku/utils/requests.nim b/waku/utils/requests.nim index 5e5b9d960..d9afd2887 100644 --- a/waku/utils/requests.nim +++ b/waku/utils/requests.nim @@ -7,4 +7,4 @@ import bearssl/rand, stew/byteutils proc generateRequestId*(rng: ref HmacDrbgContext): string = var bytes: array[10, byte] hmacDrbgGenerate(rng[], bytes) - return toHex(bytes) + return byteutils.toHex(bytes) diff --git a/waku/waku_archive/driver/postgres_driver/postgres_driver.nim b/waku/waku_archive/driver/postgres_driver/postgres_driver.nim index 9b0e14c84..4877cb126 100644 --- a/waku/waku_archive/driver/postgres_driver/postgres_driver.nim +++ b/waku/waku_archive/driver/postgres_driver/postgres_driver.nim @@ -297,13 +297,13 @@ method put*( pubsubTopic: PubsubTopic, message: WakuMessage, ): Future[ArchiveDriverResult[void]] {.async.} = - let messageHash = toHex(messageHash) + let messageHash = byteutils.toHex(messageHash) let contentTopic = message.contentTopic - let payload = toHex(message.payload) + let payload = byteutils.toHex(message.payload) let version = $message.version let timestamp = $message.timestamp - let meta = toHex(message.meta) + let meta = byteutils.toHex(message.meta) trace "put PostgresDriver", messageHash, contentTopic, payload, version, timestamp, meta @@ -439,7 +439,7 @@ proc getMessagesArbitraryQuery( var args: seq[string] if cursor.isSome(): - let hashHex = toHex(cursor.get()) + let hashHex = byteutils.toHex(cursor.get()) let timeCursor = ?await s.getTimeCursor(hashHex) @@ -520,7 +520,7 @@ proc getMessageHashesArbitraryQuery( var args: seq[string] if cursor.isSome(): - let hashHex = toHex(cursor.get()) + let hashHex = byteutils.toHex(cursor.get()) let timeCursor = ?await s.getTimeCursor(hashHex) @@ -630,7 +630,7 @@ proc getMessagesPreparedStmt( return ok(rows) - let hashHex = toHex(cursor.get()) + let hashHex = byteutils.toHex(cursor.get()) let timeCursor = ?await s.getTimeCursor(hashHex) @@ -723,7 +723,7 @@ proc getMessageHashesPreparedStmt( return ok(rows) - let hashHex = toHex(cursor.get()) + let hashHex = byteutils.toHex(cursor.get()) let timeCursor = ?await s.getTimeCursor(hashHex) 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 1a39c1267..a6784e4f8 100644 --- a/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim +++ b/waku/waku_archive_legacy/driver/postgres_driver/postgres_driver.nim @@ -213,13 +213,13 @@ method put*( messageHash: WakuMessageHash, receivedTime: Timestamp, ): Future[ArchiveDriverResult[void]] {.async.} = - let digest = toHex(digest.data) - let messageHash = toHex(messageHash) + let digest = byteutils.toHex(digest.data) + let messageHash = byteutils.toHex(messageHash) let contentTopic = message.contentTopic - let payload = toHex(message.payload) + let payload = byteutils.toHex(message.payload) let version = $message.version let timestamp = $message.timestamp - let meta = toHex(message.meta) + let meta = byteutils.toHex(message.meta) trace "put PostgresDriver", timestamp = timestamp @@ -312,7 +312,7 @@ proc getMessagesArbitraryQuery( args.add(pubsubTopic.get()) if cursor.isSome(): - let hashHex = toHex(cursor.get().hash) + let hashHex = byteutils.toHex(cursor.get().hash) var entree: seq[(PubsubTopic, WakuMessage, seq[byte], Timestamp, WakuMessageHash)] proc entreeCallback(pqResult: ptr PGresult) = @@ -463,7 +463,7 @@ proc getMessagesPreparedStmt( let limit = $maxPageSize if cursor.isSome(): - let hash = toHex(cursor.get().hash) + let hash = byteutils.toHex(cursor.get().hash) var entree: seq[(PubsubTopic, WakuMessage, seq[byte], Timestamp, WakuMessageHash)] @@ -576,7 +576,7 @@ proc getMessagesV2PreparedStmt( var stmtDef = if ascOrder: SelectWithCursorV2AscStmtDef else: SelectWithCursorV2DescStmtDef - let digest = toHex(cursor.get().digest.data) + let digest = byteutils.toHex(cursor.get().digest.data) let timestamp = $cursor.get().storeTime ( diff --git a/waku/waku_filter_v2/client.nim b/waku/waku_filter_v2/client.nim index 323cc6da8..ba8cd3d0c 100644 --- a/waku/waku_filter_v2/client.nim +++ b/waku/waku_filter_v2/client.nim @@ -29,7 +29,7 @@ type WakuFilterClient* = ref object of LPProtocol func generateRequestId(rng: ref HmacDrbgContext): string = var bytes: array[10, byte] hmacDrbgGenerate(rng[], bytes) - return toHex(bytes) + return byteutils.toHex(bytes) proc sendSubscribeRequest( wfc: WakuFilterClient, diff --git a/waku/waku_rln_relay/rln_relay.nim b/waku/waku_rln_relay/rln_relay.nim index 8758a7bcd..5c893e2a2 100644 --- a/waku/waku_rln_relay/rln_relay.nim +++ b/waku/waku_rln_relay/rln_relay.nim @@ -346,7 +346,7 @@ proc generateRlnValidator*( let validationRes = wakuRlnRelay.validateMessageAndUpdateLog(message) let - proof = toHex(msgProof.proof) + proof = byteutils.toHex(msgProof.proof) epoch = fromEpoch(msgProof.epoch) root = inHex(msgProof.merkleRoot) shareX = inHex(msgProof.shareX) diff --git a/waku/waku_store_sync/reconciliation.nim b/waku/waku_store_sync/reconciliation.nim index 0cc15d0df..23f513322 100644 --- a/waku/waku_store_sync/reconciliation.nim +++ b/waku/waku_store_sync/reconciliation.nim @@ -79,7 +79,8 @@ proc messageIngress*( let id = SyncID(time: msg.timestamp, hash: msgHash) self.storage.insert(id, pubsubTopic, msg.contentTopic).isOkOr: - error "failed to insert new message", msg_hash = $id.hash.toHex(), error = $error + error "failed to insert new message", + msg_hash = byteutils.toHex(id.hash), error = $error proc messageIngress*( self: SyncReconciliation, @@ -87,7 +88,7 @@ proc messageIngress*( pubsubTopic: PubsubTopic, msg: WakuMessage, ) = - trace "message ingress", msg_hash = msgHash.toHex(), msg = msg + trace "message ingress", msg_hash = byteutils.toHex(msgHash), msg = msg if msg.ephemeral: return @@ -95,7 +96,8 @@ proc messageIngress*( let id = SyncID(time: msg.timestamp, hash: msgHash) self.storage.insert(id, pubsubTopic, msg.contentTopic).isOkOr: - error "failed to insert new message", msg_hash = $id.hash.toHex(), error = $error + error "failed to insert new message", + msg_hash = byteutils.toHex(id.hash), error = $error proc messageIngress*( self: SyncReconciliation, @@ -104,7 +106,8 @@ proc messageIngress*( contentTopic: ContentTopic, ) = self.storage.insert(id, pubsubTopic, contentTopic).isOkOr: - error "failed to insert new message", msg_hash = $id.hash.toHex(), error = $error + error "failed to insert new message", + msg_hash = byteutils.toHex(id.hash), error = $error proc preProcessPayload( self: SyncReconciliation, payload: RangesData From a8bdbca98a3d2eeee8aaff05ce037cd1dc32bde9 Mon Sep 17 00:00:00 2001 From: Fabiana Cecin Date: Wed, 11 Feb 2026 10:36:37 -0300 Subject: [PATCH 3/8] Simplify NodeHealthMonitor creation (#3716) Simplify NodeHealthMonitor creation * Force NodeHealthMonitor.new() to set up a WakuNode * Remove all checks for isNil(node) in NodeHealthMonitor * Fix tests to use the new NodeHealthMonitor.new() Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> --- tests/test_waku_keepalive.nim | 3 +- tests/wakunode_rest/test_rest_health.nim | 22 ++---- waku/factory/waku.nim | 20 ++---- .../health_monitor/node_health_monitor.nim | 69 ++++++------------- 4 files changed, 35 insertions(+), 79 deletions(-) diff --git a/tests/test_waku_keepalive.nim b/tests/test_waku_keepalive.nim index c12f20a05..5d8402268 100644 --- a/tests/test_waku_keepalive.nim +++ b/tests/test_waku_keepalive.nim @@ -44,8 +44,7 @@ suite "Waku Keepalive": await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) - let healthMonitor = NodeHealthMonitor() - healthMonitor.setNodeToHealthMonitor(node1) + let healthMonitor = NodeHealthMonitor.new(node1) healthMonitor.startKeepalive(2.seconds).isOkOr: assert false, "Failed to start keepalive" diff --git a/tests/wakunode_rest/test_rest_health.nim b/tests/wakunode_rest/test_rest_health.nim index ed8269f55..2a70fee5f 100644 --- a/tests/wakunode_rest/test_rest_health.nim +++ b/tests/wakunode_rest/test_rest_health.nim @@ -50,33 +50,22 @@ suite "Waku v2 REST API - health": asyncTest "Get node health info - GET /health": # Given let node = testWakuNode() - let healthMonitor = NodeHealthMonitor() await node.start() (await node.mountRelay()).isOkOr: assert false, "Failed to mount relay" - healthMonitor.setOverallHealth(HealthStatus.INITIALIZING) - var restPort = Port(0) let restAddress = parseIpAddress("0.0.0.0") let restServer = WakuRestServerRef.init(restAddress, restPort).tryGet() restPort = restServer.httpServer.address.port # update with bound port for client use + let healthMonitor = NodeHealthMonitor.new(node) + installHealthApiHandler(restServer.router, healthMonitor) restServer.start() let client = newRestHttpClient(initTAddress(restAddress, restPort)) - # When - var response = await client.healthCheck() - - # Then - check: - response.status == 200 - $response.contentType == $MIMETYPE_JSON - response.data == - HealthReport(nodeHealth: HealthStatus.INITIALIZING, protocolsHealth: @[]) - - # now kick in rln (currently the only check for health) + # kick in rln (currently the only check for health) await node.mountRlnRelay( getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) ) @@ -84,10 +73,11 @@ suite "Waku v2 REST API - health": node.mountLightPushClient() await node.mountFilterClient() - healthMonitor.setNodeToHealthMonitor(node) + # We don't have a Waku, so we need to set the overall health to READY here in its behalf healthMonitor.setOverallHealth(HealthStatus.READY) + # When - response = await client.healthCheck() + var response = await client.healthCheck() # Then check: diff --git a/waku/factory/waku.nim b/waku/factory/waku.nim index c452d44c5..3748847f1 100644 --- a/waku/factory/waku.nim +++ b/waku/factory/waku.nim @@ -172,7 +172,13 @@ proc new*( ?wakuConf.validate() wakuConf.logConf() - let healthMonitor = NodeHealthMonitor.new(wakuConf.dnsAddrsNameServers) + let relay = newCircuitRelay(wakuConf.circuitRelayClient) + + let node = (await setupNode(wakuConf, rng, relay)).valueOr: + error "Failed setting up node", error = $error + return err("Failed setting up node: " & $error) + + let healthMonitor = NodeHealthMonitor.new(node, wakuConf.dnsAddrsNameServers) let restServer: WakuRestServerRef = if wakuConf.restServerConf.isSome(): @@ -186,18 +192,6 @@ proc new*( else: nil - var relay = newCircuitRelay(wakuConf.circuitRelayClient) - - let node = (await setupNode(wakuConf, rng, relay)).valueOr: - error "Failed setting up node", error = $error - return err("Failed setting up node: " & $error) - - healthMonitor.setNodeToHealthMonitor(node) - healthMonitor.onlineMonitor.setPeerStoreToOnlineMonitor(node.switch.peerStore) - healthMonitor.onlineMonitor.addOnlineStateObserver( - node.peerManager.getOnlineStateObserver() - ) - node.setupAppCallbacks(wakuConf, appCallbacks).isOkOr: error "Failed setting up app callbacks", error = error return err("Failed setting up app callbacks: " & $error) diff --git a/waku/node/health_monitor/node_health_monitor.nim b/waku/node/health_monitor/node_health_monitor.nim index eb5d0ed8c..4b13dfd3d 100644 --- a/waku/node/health_monitor/node_health_monitor.nim +++ b/waku/node/health_monitor/node_health_monitor.nim @@ -33,14 +33,8 @@ type onlineMonitor*: OnlineMonitor keepAliveFut: Future[void] -template checkWakuNodeNotNil(node: WakuNode, p: ProtocolHealth): untyped = - if node.isNil(): - warn "WakuNode is not set, cannot check health", protocol_health_instance = $p - return p.notMounted() - proc getRelayHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Relay") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuRelay == nil: return p.notMounted() @@ -55,10 +49,6 @@ proc getRelayHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getRlnRelayHealth(hm: NodeHealthMonitor): Future[ProtocolHealth] {.async.} = var p = ProtocolHealth.init("Rln Relay") - if hm.node.isNil(): - warn "WakuNode is not set, cannot check health", protocol_health_instance = $p - return p.notMounted() - if hm.node.wakuRlnRelay.isNil(): return p.notMounted() @@ -83,7 +73,6 @@ proc getLightpushHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = var p = ProtocolHealth.init("Lightpush") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLightPush == nil: return p.notMounted() @@ -97,7 +86,6 @@ proc getLightpushClientHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = var p = ProtocolHealth.init("Lightpush Client") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLightpushClient == nil: return p.notMounted() @@ -115,7 +103,6 @@ proc getLegacyLightpushHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = var p = ProtocolHealth.init("Legacy Lightpush") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLegacyLightPush == nil: return p.notMounted() @@ -129,7 +116,6 @@ proc getLegacyLightpushClientHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = var p = ProtocolHealth.init("Legacy Lightpush Client") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLegacyLightpushClient == nil: return p.notMounted() @@ -142,7 +128,6 @@ proc getLegacyLightpushClientHealth( proc getFilterHealth(hm: NodeHealthMonitor, relayHealth: HealthStatus): ProtocolHealth = var p = ProtocolHealth.init("Filter") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuFilter == nil: return p.notMounted() @@ -156,7 +141,6 @@ proc getFilterClientHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = var p = ProtocolHealth.init("Filter Client") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuFilterClient == nil: return p.notMounted() @@ -168,7 +152,6 @@ proc getFilterClientHealth( proc getStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Store") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuStore == nil: return p.notMounted() @@ -177,7 +160,6 @@ proc getStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Store Client") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuStoreClient == nil: return p.notMounted() @@ -191,7 +173,6 @@ proc getStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getLegacyStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Legacy Store") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLegacyStore == nil: return p.notMounted() @@ -200,7 +181,6 @@ proc getLegacyStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getLegacyStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Legacy Store Client") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuLegacyStoreClient == nil: return p.notMounted() @@ -215,7 +195,6 @@ proc getLegacyStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getPeerExchangeHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Peer Exchange") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuPeerExchange == nil: return p.notMounted() @@ -224,7 +203,6 @@ proc getPeerExchangeHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getRendezvousHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Rendezvous") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuRendezvous == nil: return p.notMounted() @@ -236,7 +214,6 @@ proc getRendezvousHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getMixHealth(hm: NodeHealthMonitor): ProtocolHealth = var p = ProtocolHealth.init("Mix") - checkWakuNodeNotNil(hm.node, p) if hm.node.wakuMix.isNil(): return p.notMounted() @@ -386,29 +363,25 @@ proc getNodeHealthReport*(hm: NodeHealthMonitor): Future[HealthReport] {.async.} var report: HealthReport report.nodeHealth = hm.nodeHealth - if not hm.node.isNil(): - let relayHealth = hm.getRelayHealth() - report.protocolsHealth.add(relayHealth) - report.protocolsHealth.add(await hm.getRlnRelayHealth()) - report.protocolsHealth.add(hm.getLightpushHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getLegacyLightpushHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getFilterHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getStoreHealth()) - report.protocolsHealth.add(hm.getLegacyStoreHealth()) - report.protocolsHealth.add(hm.getPeerExchangeHealth()) - report.protocolsHealth.add(hm.getRendezvousHealth()) - report.protocolsHealth.add(hm.getMixHealth()) + let relayHealth = hm.getRelayHealth() + report.protocolsHealth.add(relayHealth) + report.protocolsHealth.add(await hm.getRlnRelayHealth()) + report.protocolsHealth.add(hm.getLightpushHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getLegacyLightpushHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getFilterHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getStoreHealth()) + report.protocolsHealth.add(hm.getLegacyStoreHealth()) + report.protocolsHealth.add(hm.getPeerExchangeHealth()) + report.protocolsHealth.add(hm.getRendezvousHealth()) + report.protocolsHealth.add(hm.getMixHealth()) - report.protocolsHealth.add(hm.getLightpushClientHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getLegacyLightpushClientHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getStoreClientHealth()) - report.protocolsHealth.add(hm.getLegacyStoreClientHealth()) - report.protocolsHealth.add(hm.getFilterClientHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getLightpushClientHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getLegacyLightpushClientHealth(relayHealth.health)) + report.protocolsHealth.add(hm.getStoreClientHealth()) + report.protocolsHealth.add(hm.getLegacyStoreClientHealth()) + report.protocolsHealth.add(hm.getFilterClientHealth(relayHealth.health)) return report -proc setNodeToHealthMonitor*(hm: NodeHealthMonitor, node: WakuNode) = - hm.node = node - proc setOverallHealth*(hm: NodeHealthMonitor, health: HealthStatus) = hm.nodeHealth = health @@ -427,10 +400,10 @@ proc stopHealthMonitor*(hm: NodeHealthMonitor) {.async.} = proc new*( T: type NodeHealthMonitor, + node: WakuNode, dnsNameServers = @[parseIpAddress("1.1.1.1"), parseIpAddress("1.0.0.1")], ): T = - T( - nodeHealth: INITIALIZING, - node: nil, - onlineMonitor: OnlineMonitor.init(dnsNameServers), - ) + let om = OnlineMonitor.init(dnsNameServers) + om.setPeerStoreToOnlineMonitor(node.switch.peerStore) + om.addOnlineStateObserver(node.peerManager.getOnlineStateObserver()) + T(nodeHealth: INITIALIZING, node: node, onlineMonitor: om) From dd8dc7429d724dff6b7254bd5206a2811223461a Mon Sep 17 00:00:00 2001 From: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:19:58 +0100 Subject: [PATCH 4/8] canary exits with error if ping fails (#3711) --- apps/wakucanary/wakucanary.nim | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/wakucanary/wakucanary.nim b/apps/wakucanary/wakucanary.nim index 6e02c2a8f..40bf4db45 100644 --- a/apps/wakucanary/wakucanary.nim +++ b/apps/wakucanary/wakucanary.nim @@ -278,6 +278,10 @@ proc main(rng: ref HmacDrbgContext): Future[int] {.async.} = pingSuccess = false error "Ping operation failed or timed out", error = exc.msg + if not pingSuccess: + error "Ping to the node failed", peerId = peer.peerId, conStatus = $conStatus + quit(QuitFailure) + if conStatus in [Connected, CanConnect]: let nodeProtocols = lp2pPeerStore[ProtoBook][peer.peerId] @@ -285,11 +289,6 @@ proc main(rng: ref HmacDrbgContext): Future[int] {.async.} = error "Not all protocols are supported", expected = conf.protocols, supported = nodeProtocols quit(QuitFailure) - - # Check ping result if ping was enabled - if conf.ping and not pingSuccess: - error "Node is reachable and supports protocols but ping failed - connection may be unstable" - quit(QuitFailure) elif conStatus == CannotConnect: error "Could not connect", peerId = peer.peerId quit(QuitFailure) From 1fb4d1eab0460b3e033e0ab87bd4ac3b6966f3c9 Mon Sep 17 00:00:00 2001 From: Fabiana Cecin Date: Thu, 12 Feb 2026 14:52:39 -0300 Subject: [PATCH 5/8] feat: implement Waku API Health spec (#3689) * Fix protocol strength metric to consider connected peers only * Remove polling loop; event-driven node connection health updates * Remove 10s WakuRelay topic health polling loop; now event-driven * Change NodeHealthStatus to ConnectionStatus * Change new nodeState (rest API /health) field to connectionStatus * Add getSyncProtocolHealthInfo and getSyncNodeHealthReport * Add ConnectionStatusChangeEvent * Add RequestHealthReport * Refactor sync/async protocol health queries in the health monitor * Add EventRelayTopicHealthChange * Add EventWakuPeer emitted by PeerManager * Add Edge support for topics health requests and events * Rename "RelayTopic" -> "Topic" * Add RequestContentTopicsHealth sync request * Add EventContentTopicHealthChange * Rename RequestTopicsHealth -> RequestShardTopicsHealth * Remove health check gating from checkApiAvailability * Add basic health smoke tests * Other misc improvements, refactors, fixes Co-authored-by: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> --- .../json_connection_status_change_event.nim | 19 + library/libwaku.nim | 8 + tests/api/test_all.nim | 2 +- tests/api/test_api_health.nim | 296 +++++++++ tests/api/test_api_send.nim | 9 +- tests/node/test_all.nim | 3 +- tests/node/test_wakunode_health_monitor.nim | 301 +++++++++ tests/waku_relay/utils.nim | 18 +- tests/wakunode_rest/test_rest_health.nim | 63 +- waku/api/api.nim | 16 +- waku/api/types.nim | 8 +- waku/common/waku_protocol.nim | 24 + waku/events/events.nim | 4 +- waku/events/health_events.nim | 27 + waku/events/peer_events.nim | 13 + waku/factory/app_callbacks.nim | 3 +- waku/factory/builder.nim | 3 +- waku/factory/waku.nim | 165 +++-- .../send_service/relay_processor.nim | 4 +- waku/node/health_monitor.nim | 9 +- .../node/health_monitor/connection_status.nim | 15 + waku/node/health_monitor/health_report.nim | 10 + .../health_monitor/node_health_monitor.nim | 598 ++++++++++++++---- waku/node/health_monitor/protocol_health.nim | 10 +- waku/node/peer_manager/peer_manager.nim | 128 +++- waku/node/peer_manager/waku_peer_store.nim | 7 +- waku/node/waku_node.nim | 126 +++- waku/requests/health_request.nim | 21 - waku/requests/health_requests.nim | 39 ++ waku/requests/requests.nim | 4 +- waku/rest_api/endpoint/health/types.nim | 22 +- waku/waku_relay/protocol.nim | 146 +++-- 32 files changed, 1727 insertions(+), 394 deletions(-) create mode 100644 library/events/json_connection_status_change_event.nim create mode 100644 tests/api/test_api_health.nim create mode 100644 tests/node/test_wakunode_health_monitor.nim create mode 100644 waku/common/waku_protocol.nim create mode 100644 waku/events/health_events.nim create mode 100644 waku/events/peer_events.nim create mode 100644 waku/node/health_monitor/connection_status.nim create mode 100644 waku/node/health_monitor/health_report.nim delete mode 100644 waku/requests/health_request.nim create mode 100644 waku/requests/health_requests.nim diff --git a/library/events/json_connection_status_change_event.nim b/library/events/json_connection_status_change_event.nim new file mode 100644 index 000000000..347a84c48 --- /dev/null +++ b/library/events/json_connection_status_change_event.nim @@ -0,0 +1,19 @@ +{.push raises: [].} + +import system, std/json +import ./json_base_event +import ../../waku/api/types + +type JsonConnectionStatusChangeEvent* = ref object of JsonEvent + status*: ConnectionStatus + +proc new*( + T: type JsonConnectionStatusChangeEvent, status: ConnectionStatus +): T = + return JsonConnectionStatusChangeEvent( + eventType: "node_health_change", + status: status + ) + +method `$`*(event: JsonConnectionStatusChangeEvent): string = + $(%*event) diff --git a/library/libwaku.nim b/library/libwaku.nim index c71e823d6..eb3cdff5e 100644 --- a/library/libwaku.nim +++ b/library/libwaku.nim @@ -7,9 +7,11 @@ import ./events/json_message_event, ./events/json_topic_health_change_event, ./events/json_connection_change_event, + ./events/json_connection_status_change_event, ../waku/factory/app_callbacks, waku/factory/waku, waku/node/waku_node, + waku/node/health_monitor/health_status, ./declare_lib ################################################################################ @@ -61,10 +63,16 @@ proc waku_new( callEventCallback(ctx, "onConnectionChange"): $JsonConnectionChangeEvent.new($peerId, peerEvent) + proc onConnectionStatusChange(ctx: ptr FFIContext): ConnectionStatusChangeHandler = + return proc(status: ConnectionStatus) {.async.} = + callEventCallback(ctx, "onConnectionStatusChange"): + $JsonConnectionStatusChangeEvent.new(status) + let appCallbacks = AppCallbacks( relayHandler: onReceivedMessage(ctx), topicHealthChangeHandler: onTopicHealthChange(ctx), connectionChangeHandler: onConnectionChange(ctx), + connectionStatusChangeHandler: onConnectionStatusChange(ctx) ) ffi.sendRequestToFFIThread( diff --git a/tests/api/test_all.nim b/tests/api/test_all.nim index 99c1b3b4c..57f7f37f2 100644 --- a/tests/api/test_all.nim +++ b/tests/api/test_all.nim @@ -1,3 +1,3 @@ {.used.} -import ./test_entry_nodes, ./test_node_conf +import ./test_entry_nodes, ./test_node_conf, ./test_api_send, ./test_api_health diff --git a/tests/api/test_api_health.nim b/tests/api/test_api_health.nim new file mode 100644 index 000000000..b7aab43f9 --- /dev/null +++ b/tests/api/test_api_health.nim @@ -0,0 +1,296 @@ +{.used.} + +import std/[options, sequtils, times] +import chronos, testutils/unittests, stew/byteutils, libp2p/[switch, peerinfo] +import ../testlib/[common, wakucore, wakunode, testasync] + +import + waku, + waku/[waku_node, waku_core, waku_relay/protocol, common/broker/broker_context], + waku/node/health_monitor/[topic_health, health_status, protocol_health, health_report], + waku/requests/health_requests, + waku/requests/node_requests, + waku/events/health_events, + waku/common/waku_protocol, + waku/factory/waku_conf + +const TestTimeout = chronos.seconds(10) +const DefaultShard = PubsubTopic("/waku/2/rs/1/0") +const TestContentTopic = ContentTopic("/waku/2/default-content/proto") + +proc dummyHandler( + topic: PubsubTopic, msg: WakuMessage +): Future[void] {.async, gcsafe.} = + discard + +proc waitForConnectionStatus( + brokerCtx: BrokerContext, expected: ConnectionStatus +) {.async.} = + var future = newFuture[void]("waitForConnectionStatus") + + let handler: EventConnectionStatusChangeListenerProc = proc( + e: EventConnectionStatusChange + ) {.async: (raises: []), gcsafe.} = + if not future.finished: + if e.connectionStatus == expected: + future.complete() + + let handle = EventConnectionStatusChange.listen(brokerCtx, handler).valueOr: + raiseAssert error + + try: + if not await future.withTimeout(TestTimeout): + raiseAssert "Timeout waiting for status: " & $expected + finally: + EventConnectionStatusChange.dropListener(brokerCtx, handle) + +proc waitForShardHealthy( + brokerCtx: BrokerContext +): Future[EventShardTopicHealthChange] {.async.} = + var future = newFuture[EventShardTopicHealthChange]("waitForShardHealthy") + + let handler: EventShardTopicHealthChangeListenerProc = proc( + e: EventShardTopicHealthChange + ) {.async: (raises: []), gcsafe.} = + if not future.finished: + if e.health == TopicHealth.MINIMALLY_HEALTHY or + e.health == TopicHealth.SUFFICIENTLY_HEALTHY: + future.complete(e) + + let handle = EventShardTopicHealthChange.listen(brokerCtx, handler).valueOr: + raiseAssert error + + try: + if await future.withTimeout(TestTimeout): + return future.read() + else: + raiseAssert "Timeout waiting for shard health event" + finally: + EventShardTopicHealthChange.dropListener(brokerCtx, handle) + +suite "LM API health checking": + var + serviceNode {.threadvar.}: WakuNode + client {.threadvar.}: Waku + servicePeerInfo {.threadvar.}: RemotePeerInfo + + asyncSetup: + lockNewGlobalBrokerContext: + serviceNode = + newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + (await serviceNode.mountRelay()).isOkOr: + raiseAssert error + serviceNode.mountMetadata(1, @[0'u16]).isOkOr: + raiseAssert error + await serviceNode.mountLibp2pPing() + await serviceNode.start() + + servicePeerInfo = serviceNode.peerInfo.toRemotePeerInfo() + serviceNode.wakuRelay.subscribe(DefaultShard, dummyHandler) + + lockNewGlobalBrokerContext: + let conf = NodeConfig.init( + mode = WakuMode.Core, + networkingConfig = + NetworkingConfig(listenIpv4: "0.0.0.0", p2pTcpPort: 0, discv5UdpPort: 0), + protocolsConfig = ProtocolsConfig.init( + entryNodes = @[], + clusterId = 1'u16, + autoShardingConfig = AutoShardingConfig(numShardsInCluster: 1), + ), + ) + + client = (await createNode(conf)).valueOr: + raiseAssert error + (await startWaku(addr client)).isOkOr: + raiseAssert error + + asyncTeardown: + discard await client.stop() + await serviceNode.stop() + + asyncTest "RequestShardTopicsHealth, check PubsubTopic health": + client.node.wakuRelay.subscribe(DefaultShard, dummyHandler) + await client.node.connectToNodes(@[servicePeerInfo]) + + var isHealthy = false + let start = Moment.now() + while Moment.now() - start < TestTimeout: + let req = RequestShardTopicsHealth.request(client.brokerCtx, @[DefaultShard]).valueOr: + raiseAssert "RequestShardTopicsHealth failed" + + if req.topicHealth.len > 0: + let h = req.topicHealth[0].health + if h == TopicHealth.MINIMALLY_HEALTHY or h == TopicHealth.SUFFICIENTLY_HEALTHY: + isHealthy = true + break + await sleepAsync(chronos.milliseconds(100)) + + check isHealthy == true + + asyncTest "RequestShardTopicsHealth, check disconnected PubsubTopic": + const GhostShard = PubsubTopic("/waku/2/rs/1/666") + client.node.wakuRelay.subscribe(GhostShard, dummyHandler) + + let req = RequestShardTopicsHealth.request(client.brokerCtx, @[GhostShard]).valueOr: + raiseAssert "Request failed" + + check req.topicHealth.len > 0 + check req.topicHealth[0].health == TopicHealth.UNHEALTHY + + asyncTest "RequestProtocolHealth, check relay status": + await client.node.connectToNodes(@[servicePeerInfo]) + + var isReady = false + let start = Moment.now() + while Moment.now() - start < TestTimeout: + let relayReq = await RequestProtocolHealth.request( + client.brokerCtx, WakuProtocol.RelayProtocol + ) + if relayReq.isOk() and relayReq.get().healthStatus.health == HealthStatus.READY: + isReady = true + break + await sleepAsync(chronos.milliseconds(100)) + + check isReady == true + + let storeReq = + await RequestProtocolHealth.request(client.brokerCtx, WakuProtocol.StoreProtocol) + if storeReq.isOk(): + check storeReq.get().healthStatus.health != HealthStatus.READY + + asyncTest "RequestProtocolHealth, check unmounted protocol": + let req = + await RequestProtocolHealth.request(client.brokerCtx, WakuProtocol.StoreProtocol) + check req.isOk() + + let status = req.get().healthStatus + check status.health == HealthStatus.NOT_MOUNTED + check status.desc.isNone() + + asyncTest "RequestConnectionStatus, check connectivity state": + let initialReq = RequestConnectionStatus.request(client.brokerCtx).valueOr: + raiseAssert "RequestConnectionStatus failed" + check initialReq.connectionStatus == ConnectionStatus.Disconnected + + await client.node.connectToNodes(@[servicePeerInfo]) + + var isConnected = false + let start = Moment.now() + while Moment.now() - start < TestTimeout: + let req = RequestConnectionStatus.request(client.brokerCtx).valueOr: + raiseAssert "RequestConnectionStatus failed" + + if req.connectionStatus == ConnectionStatus.PartiallyConnected or + req.connectionStatus == ConnectionStatus.Connected: + isConnected = true + break + await sleepAsync(chronos.milliseconds(100)) + + check isConnected == true + + asyncTest "EventConnectionStatusChange, detect connect and disconnect": + let connectFuture = + waitForConnectionStatus(client.brokerCtx, ConnectionStatus.PartiallyConnected) + + await client.node.connectToNodes(@[servicePeerInfo]) + await connectFuture + + let disconnectFuture = + waitForConnectionStatus(client.brokerCtx, ConnectionStatus.Disconnected) + await client.node.disconnectNode(servicePeerInfo) + await disconnectFuture + + asyncTest "EventShardTopicHealthChange, detect health improvement": + client.node.wakuRelay.subscribe(DefaultShard, dummyHandler) + + let healthEventFuture = waitForShardHealthy(client.brokerCtx) + + await client.node.connectToNodes(@[servicePeerInfo]) + + let event = await healthEventFuture + check event.topic == DefaultShard + + asyncTest "RequestHealthReport, check aggregate report": + let req = await RequestHealthReport.request(client.brokerCtx) + + check req.isOk() + + let report = req.get().healthReport + check report.nodeHealth == HealthStatus.READY + check report.protocolsHealth.len > 0 + check report.protocolsHealth.anyIt(it.protocol == $WakuProtocol.RelayProtocol) + + asyncTest "RequestContentTopicsHealth, smoke test": + let fictionalTopic = ContentTopic("/waku/2/this-does-not-exist/proto") + + let req = RequestContentTopicsHealth.request(client.brokerCtx, @[fictionalTopic]) + + check req.isOk() + + let res = req.get() + check res.contentTopicHealth.len == 1 + check res.contentTopicHealth[0].topic == fictionalTopic + check res.contentTopicHealth[0].health == TopicHealth.NOT_SUBSCRIBED + + asyncTest "RequestContentTopicsHealth, core mode trivial 1-shard autosharding": + let cTopic = ContentTopic("/waku/2/my-content-topic/proto") + + let shardReq = + RequestRelayShard.request(client.brokerCtx, none(PubsubTopic), cTopic) + + check shardReq.isOk() + let targetShard = $shardReq.get().relayShard + + client.node.wakuRelay.subscribe(targetShard, dummyHandler) + serviceNode.wakuRelay.subscribe(targetShard, dummyHandler) + + await client.node.connectToNodes(@[servicePeerInfo]) + + var isHealthy = false + let start = Moment.now() + while Moment.now() - start < TestTimeout: + let req = RequestContentTopicsHealth.request(client.brokerCtx, @[cTopic]).valueOr: + raiseAssert "Request failed" + + if req.contentTopicHealth.len > 0: + let h = req.contentTopicHealth[0].health + if h == TopicHealth.MINIMALLY_HEALTHY or h == TopicHealth.SUFFICIENTLY_HEALTHY: + isHealthy = true + break + + await sleepAsync(chronos.milliseconds(100)) + + check isHealthy == true + + asyncTest "RequestProtocolHealth, edge mode smoke test": + var edgeWaku: Waku + + lockNewGlobalBrokerContext: + let edgeConf = NodeConfig.init( + mode = WakuMode.Edge, + networkingConfig = + NetworkingConfig(listenIpv4: "0.0.0.0", p2pTcpPort: 0, discv5UdpPort: 0), + protocolsConfig = ProtocolsConfig.init( + entryNodes = @[], + clusterId = 1'u16, + messageValidation = + MessageValidation(maxMessageSize: "150 KiB", rlnConfig: none(RlnConfig)), + ), + ) + + edgeWaku = (await createNode(edgeConf)).valueOr: + raiseAssert "Failed to create edge node: " & error + + (await startWaku(addr edgeWaku)).isOkOr: + raiseAssert "Failed to start edge waku: " & error + + let relayReq = await RequestProtocolHealth.request( + edgeWaku.brokerCtx, WakuProtocol.RelayProtocol + ) + check relayReq.isOk() + check relayReq.get().healthStatus.health == HealthStatus.NOT_MOUNTED + + check not edgeWaku.node.wakuFilterClient.isNil() + + discard await edgeWaku.stop() diff --git a/tests/api/test_api_send.nim b/tests/api/test_api_send.nim index e247c65ce..7343fc655 100644 --- a/tests/api/test_api_send.nim +++ b/tests/api/test_api_send.nim @@ -117,6 +117,9 @@ proc validate( check requestId == expectedRequestId proc createApiNodeConf(mode: WakuMode = WakuMode.Core): NodeConfig = + # allocate random ports to avoid port-already-in-use errors + let netConf = NetworkingConfig(listenIpv4: "0.0.0.0", p2pTcpPort: 0, discv5UdpPort: 0) + result = NodeConfig.init( mode = mode, protocolsConfig = ProtocolsConfig.init( @@ -124,6 +127,7 @@ proc createApiNodeConf(mode: WakuMode = WakuMode.Core): NodeConfig = clusterId = 1, autoShardingConfig = AutoShardingConfig(numShardsInCluster: 1), ), + networkingConfig = netConf, p2pReliability = true, ) @@ -246,8 +250,9 @@ suite "Waku API - Send": let sendResult = await node.send(envelope) - check sendResult.isErr() # Depending on implementation, it might say "not healthy" - check sendResult.error().contains("not healthy") + # TODO: The API is not enforcing a health check before the send, + # so currently this test cannot successfully fail to send. + check sendResult.isOk() (await node.stop()).isOkOr: raiseAssert "Failed to stop node: " & error diff --git a/tests/node/test_all.nim b/tests/node/test_all.nim index f6e7507b7..fe785dee2 100644 --- a/tests/node/test_all.nim +++ b/tests/node/test_all.nim @@ -7,4 +7,5 @@ import ./test_wakunode_peer_exchange, ./test_wakunode_store, ./test_wakunode_legacy_store, - ./test_wakunode_peer_manager + ./test_wakunode_peer_manager, + ./test_wakunode_health_monitor diff --git a/tests/node/test_wakunode_health_monitor.nim b/tests/node/test_wakunode_health_monitor.nim new file mode 100644 index 000000000..8be9c444d --- /dev/null +++ b/tests/node/test_wakunode_health_monitor.nim @@ -0,0 +1,301 @@ +{.used.} + +import + std/[json, options, sequtils, strutils, tables], testutils/unittests, chronos, results + +import + waku/[ + waku_core, + common/waku_protocol, + node/waku_node, + node/peer_manager, + node/health_monitor/health_status, + node/health_monitor/connection_status, + node/health_monitor/protocol_health, + node/health_monitor/node_health_monitor, + node/kernel_api/relay, + node/kernel_api/store, + node/kernel_api/lightpush, + node/kernel_api/filter, + waku_archive, + ] + +import ../testlib/[wakunode, wakucore], ../waku_archive/archive_utils + +const MockDLow = 4 # Mocked GossipSub DLow value + +const TestConnectivityTimeLimit = 3.seconds + +proc protoHealthMock(kind: WakuProtocol, health: HealthStatus): ProtocolHealth = + var ph = ProtocolHealth.init(kind) + if health == HealthStatus.READY: + return ph.ready() + else: + return ph.notReady("mock") + +suite "Health Monitor - health state calculation": + test "Disconnected, zero peers": + let protocols = + @[ + protoHealthMock(RelayProtocol, HealthStatus.NOT_READY), + protoHealthMock(StoreClientProtocol, HealthStatus.NOT_READY), + protoHealthMock(FilterClientProtocol, HealthStatus.NOT_READY), + protoHealthMock(LightpushClientProtocol, HealthStatus.NOT_READY), + ] + let strength = initTable[WakuProtocol, int]() + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.Disconnected + + test "PartiallyConnected, weak relay": + let weakCount = MockDLow - 1 + let protocols = @[protoHealthMock(RelayProtocol, HealthStatus.READY)] + var strength = initTable[WakuProtocol, int]() + strength[RelayProtocol] = weakCount + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + # Partially connected since relay connectivity is weak (> 0, but < dLow) + check state == ConnectionStatus.PartiallyConnected + + test "Connected, robust relay": + let protocols = @[protoHealthMock(RelayProtocol, HealthStatus.READY)] + var strength = initTable[WakuProtocol, int]() + strength[RelayProtocol] = MockDLow + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + # Fully connected since relay connectivity is ideal (>= dLow) + check state == ConnectionStatus.Connected + + test "Connected, robust edge": + let protocols = + @[ + protoHealthMock(RelayProtocol, HealthStatus.NOT_MOUNTED), + protoHealthMock(LightpushClientProtocol, HealthStatus.READY), + protoHealthMock(FilterClientProtocol, HealthStatus.READY), + protoHealthMock(StoreClientProtocol, HealthStatus.READY), + ] + var strength = initTable[WakuProtocol, int]() + strength[LightpushClientProtocol] = HealthyThreshold + strength[FilterClientProtocol] = HealthyThreshold + strength[StoreClientProtocol] = HealthyThreshold + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.Connected + + test "Disconnected, edge missing store": + let protocols = + @[ + protoHealthMock(LightpushClientProtocol, HealthStatus.READY), + protoHealthMock(FilterClientProtocol, HealthStatus.READY), + protoHealthMock(StoreClientProtocol, HealthStatus.NOT_READY), + ] + var strength = initTable[WakuProtocol, int]() + strength[LightpushClientProtocol] = HealthyThreshold + strength[FilterClientProtocol] = HealthyThreshold + strength[StoreClientProtocol] = 0 + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.Disconnected + + test "PartiallyConnected, edge meets minimum failover requirement": + let weakCount = max(1, HealthyThreshold - 1) + let protocols = + @[ + protoHealthMock(LightpushClientProtocol, HealthStatus.READY), + protoHealthMock(FilterClientProtocol, HealthStatus.READY), + protoHealthMock(StoreClientProtocol, HealthStatus.READY), + ] + var strength = initTable[WakuProtocol, int]() + strength[LightpushClientProtocol] = weakCount + strength[FilterClientProtocol] = weakCount + strength[StoreClientProtocol] = weakCount + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.PartiallyConnected + + test "Connected, robust relay ignores store server": + let protocols = + @[ + protoHealthMock(RelayProtocol, HealthStatus.READY), + protoHealthMock(StoreProtocol, HealthStatus.READY), + ] + var strength = initTable[WakuProtocol, int]() + strength[RelayProtocol] = MockDLow + strength[StoreProtocol] = 0 + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.Connected + + test "Connected, robust relay ignores store client": + let protocols = + @[ + protoHealthMock(RelayProtocol, HealthStatus.READY), + protoHealthMock(StoreProtocol, HealthStatus.READY), + protoHealthMock(StoreClientProtocol, HealthStatus.NOT_READY), + ] + var strength = initTable[WakuProtocol, int]() + strength[RelayProtocol] = MockDLow + strength[StoreProtocol] = 0 + strength[StoreClientProtocol] = 0 + let state = calculateConnectionState(protocols, strength, some(MockDLow)) + check state == ConnectionStatus.Connected + +suite "Health Monitor - events": + asyncTest "Core (relay) health update": + let + nodeAKey = generateSecp256k1Key() + nodeA = newTestWakuNode(nodeAKey, parseIpAddress("127.0.0.1"), Port(0)) + + (await nodeA.mountRelay()).expect("Node A failed to mount Relay") + + await nodeA.start() + + let monitorA = NodeHealthMonitor.new(nodeA) + + var + lastStatus = ConnectionStatus.Disconnected + callbackCount = 0 + healthChangeSignal = newAsyncEvent() + + monitorA.onConnectionStatusChange = proc(status: ConnectionStatus) {.async.} = + lastStatus = status + callbackCount.inc() + healthChangeSignal.fire() + + monitorA.startHealthMonitor().expect("Health monitor failed to start") + + let + nodeBKey = generateSecp256k1Key() + nodeB = newTestWakuNode(nodeBKey, parseIpAddress("127.0.0.1"), Port(0)) + + let driver = newSqliteArchiveDriver() + nodeB.mountArchive(driver).expect("Node B failed to mount archive") + + (await nodeB.mountRelay()).expect("Node B failed to mount relay") + await nodeB.mountStore() + + await nodeB.start() + + await nodeA.connectToNodes(@[nodeB.switch.peerInfo.toRemotePeerInfo()]) + + proc dummyHandler(topic: PubsubTopic, msg: WakuMessage): Future[void] {.async.} = + discard + + nodeA.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), dummyHandler).expect( + "Node A failed to subscribe" + ) + nodeB.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), dummyHandler).expect( + "Node B failed to subscribe" + ) + + let connectTimeLimit = Moment.now() + TestConnectivityTimeLimit + var gotConnected = false + + while Moment.now() < connectTimeLimit: + if lastStatus == ConnectionStatus.PartiallyConnected: + gotConnected = true + break + + if await healthChangeSignal.wait().withTimeout(connectTimeLimit - Moment.now()): + healthChangeSignal.clear() + + check: + gotConnected == true + callbackCount >= 1 + lastStatus == ConnectionStatus.PartiallyConnected + + healthChangeSignal.clear() + + await nodeB.stop() + await nodeA.disconnectNode(nodeB.switch.peerInfo.toRemotePeerInfo()) + + let disconnectTimeLimit = Moment.now() + TestConnectivityTimeLimit + var gotDisconnected = false + + while Moment.now() < disconnectTimeLimit: + if lastStatus == ConnectionStatus.Disconnected: + gotDisconnected = true + break + + if await healthChangeSignal.wait().withTimeout(disconnectTimeLimit - Moment.now()): + healthChangeSignal.clear() + + check: + gotDisconnected == true + + await monitorA.stopHealthMonitor() + await nodeA.stop() + + asyncTest "Edge (light client) health update": + let + nodeAKey = generateSecp256k1Key() + nodeA = newTestWakuNode(nodeAKey, parseIpAddress("127.0.0.1"), Port(0)) + + nodeA.mountLightpushClient() + await nodeA.mountFilterClient() + nodeA.mountStoreClient() + + await nodeA.start() + + let monitorA = NodeHealthMonitor.new(nodeA) + + var + lastStatus = ConnectionStatus.Disconnected + callbackCount = 0 + healthChangeSignal = newAsyncEvent() + + monitorA.onConnectionStatusChange = proc(status: ConnectionStatus) {.async.} = + lastStatus = status + callbackCount.inc() + healthChangeSignal.fire() + + monitorA.startHealthMonitor().expect("Health monitor failed to start") + + let + nodeBKey = generateSecp256k1Key() + nodeB = newTestWakuNode(nodeBKey, parseIpAddress("127.0.0.1"), Port(0)) + + let driver = newSqliteArchiveDriver() + nodeB.mountArchive(driver).expect("Node B failed to mount archive") + + (await nodeB.mountRelay()).expect("Node B failed to mount relay") + + (await nodeB.mountLightpush()).expect("Node B failed to mount lightpush") + await nodeB.mountFilter() + await nodeB.mountStore() + + await nodeB.start() + + await nodeA.connectToNodes(@[nodeB.switch.peerInfo.toRemotePeerInfo()]) + + let connectTimeLimit = Moment.now() + TestConnectivityTimeLimit + var gotConnected = false + + while Moment.now() < connectTimeLimit: + if lastStatus == ConnectionStatus.PartiallyConnected: + gotConnected = true + break + + if await healthChangeSignal.wait().withTimeout(connectTimeLimit - Moment.now()): + healthChangeSignal.clear() + + check: + gotConnected == true + callbackCount >= 1 + lastStatus == ConnectionStatus.PartiallyConnected + + healthChangeSignal.clear() + + await nodeB.stop() + await nodeA.disconnectNode(nodeB.switch.peerInfo.toRemotePeerInfo()) + + let disconnectTimeLimit = Moment.now() + TestConnectivityTimeLimit + var gotDisconnected = false + + while Moment.now() < disconnectTimeLimit: + if lastStatus == ConnectionStatus.Disconnected: + gotDisconnected = true + break + + if await healthChangeSignal.wait().withTimeout(disconnectTimeLimit - Moment.now()): + healthChangeSignal.clear() + + check: + gotDisconnected == true + lastStatus == ConnectionStatus.Disconnected + + await monitorA.stopHealthMonitor() + await nodeA.stop() diff --git a/tests/waku_relay/utils.nim b/tests/waku_relay/utils.nim index d5703d415..4e958a4ea 100644 --- a/tests/waku_relay/utils.nim +++ b/tests/waku_relay/utils.nim @@ -11,15 +11,15 @@ import from std/times import epochTime import - waku/ - [ - waku_relay, - node/waku_node, - node/peer_manager, - waku_core, - waku_node, - waku_rln_relay, - ], + waku/[ + waku_relay, + node/waku_node, + node/peer_manager, + waku_core, + waku_node, + waku_rln_relay, + common/broker/broker_context, + ], ../waku_store/store_utils, ../waku_archive/archive_utils, ../testlib/[wakucore, futures] diff --git a/tests/wakunode_rest/test_rest_health.nim b/tests/wakunode_rest/test_rest_health.nim index 2a70fee5f..37abaf4f5 100644 --- a/tests/wakunode_rest/test_rest_health.nim +++ b/tests/wakunode_rest/test_rest_health.nim @@ -10,6 +10,7 @@ import libp2p/crypto/crypto import waku/[ + common/waku_protocol, waku_node, node/waku_node as waku_node2, # TODO: Remove after moving `git_version` to the app code. @@ -78,47 +79,39 @@ suite "Waku v2 REST API - health": # When var response = await client.healthCheck() + let report = response.data # Then check: response.status == 200 $response.contentType == $MIMETYPE_JSON - response.data.nodeHealth == HealthStatus.READY - response.data.protocolsHealth.len() == 15 - response.data.protocolsHealth[0].protocol == "Relay" - response.data.protocolsHealth[0].health == HealthStatus.NOT_READY - response.data.protocolsHealth[0].desc == some("No connected peers") - response.data.protocolsHealth[1].protocol == "Rln Relay" - response.data.protocolsHealth[1].health == HealthStatus.READY - response.data.protocolsHealth[2].protocol == "Lightpush" - response.data.protocolsHealth[2].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[3].protocol == "Legacy Lightpush" - response.data.protocolsHealth[3].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[4].protocol == "Filter" - response.data.protocolsHealth[4].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[5].protocol == "Store" - response.data.protocolsHealth[5].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[6].protocol == "Legacy Store" - response.data.protocolsHealth[6].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[7].protocol == "Peer Exchange" - response.data.protocolsHealth[7].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[8].protocol == "Rendezvous" - response.data.protocolsHealth[8].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[9].protocol == "Mix" - response.data.protocolsHealth[9].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[10].protocol == "Lightpush Client" - response.data.protocolsHealth[10].health == HealthStatus.NOT_READY - response.data.protocolsHealth[10].desc == + report.nodeHealth == HealthStatus.READY + report.protocolsHealth.len() == 15 + + report.getHealth(RelayProtocol).health == HealthStatus.NOT_READY + report.getHealth(RelayProtocol).desc == some("No connected peers") + + report.getHealth(RlnRelayProtocol).health == HealthStatus.READY + + report.getHealth(LightpushProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(LegacyLightpushProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(FilterProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(StoreProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(LegacyStoreProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(PeerExchangeProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(RendezvousProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(MixProtocol).health == HealthStatus.NOT_MOUNTED + + report.getHealth(LightpushClientProtocol).health == HealthStatus.NOT_READY + report.getHealth(LightpushClientProtocol).desc == some("No Lightpush service peer available yet") - response.data.protocolsHealth[11].protocol == "Legacy Lightpush Client" - response.data.protocolsHealth[11].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[12].protocol == "Store Client" - response.data.protocolsHealth[12].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[13].protocol == "Legacy Store Client" - response.data.protocolsHealth[13].health == HealthStatus.NOT_MOUNTED - response.data.protocolsHealth[14].protocol == "Filter Client" - response.data.protocolsHealth[14].health == HealthStatus.NOT_READY - response.data.protocolsHealth[14].desc == + + report.getHealth(LegacyLightpushClientProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(StoreClientProtocol).health == HealthStatus.NOT_MOUNTED + report.getHealth(LegacyStoreClientProtocol).health == HealthStatus.NOT_MOUNTED + + report.getHealth(FilterClientProtocol).health == HealthStatus.NOT_READY + report.getHealth(FilterClientProtocol).desc == some("No Filter service peer available yet") await restServer.stop() diff --git a/waku/api/api.nim b/waku/api/api.nim index 41f4fd240..7f13919b3 100644 --- a/waku/api/api.nim +++ b/waku/api/api.nim @@ -1,7 +1,7 @@ -import chronicles, chronos, results +import chronicles, chronos, results, std/strutils import waku/factory/waku -import waku/[requests/health_request, waku_core, waku_node] +import waku/[requests/health_requests, waku_core, waku_node] import waku/node/delivery_service/send_service import waku/node/delivery_service/subscription_service import ./[api_conf, types] @@ -25,16 +25,8 @@ proc checkApiAvailability(w: Waku): Result[void, string] = if w.isNil(): return err("Waku node is not initialized") - # check if health is satisfactory - # If Node is not healthy, return err("Waku node is not healthy") - let healthStatus = RequestNodeHealth.request(w.brokerCtx) - - if healthStatus.isErr(): - warn "Failed to get Waku node health status: ", error = healthStatus.error - # Let's suppose the node is hesalthy enough, go ahead - else: - if healthStatus.get().healthStatus == NodeHealth.Unhealthy: - return err("Waku node is not healthy, has got no connections.") + # TODO: Conciliate request-bouncing health checks here with unit testing. + # (For now, better to just allow all sends and rely on retries.) return ok() diff --git a/waku/api/types.nim b/waku/api/types.nim index a0626e98c..9eae503c8 100644 --- a/waku/api/types.nim +++ b/waku/api/types.nim @@ -14,10 +14,10 @@ type RequestId* = distinct string - NodeHealth* {.pure.} = enum - Healthy - MinimallyHealthy - Unhealthy + ConnectionStatus* {.pure.} = enum + Disconnected + PartiallyConnected + Connected proc new*(T: typedesc[RequestId], rng: ref HmacDrbgContext): T = ## Generate a new RequestId using the provided RNG. diff --git a/waku/common/waku_protocol.nim b/waku/common/waku_protocol.nim new file mode 100644 index 000000000..5063f4c98 --- /dev/null +++ b/waku/common/waku_protocol.nim @@ -0,0 +1,24 @@ +{.push raises: [].} + +type WakuProtocol* {.pure.} = enum + RelayProtocol = "Relay" + RlnRelayProtocol = "Rln Relay" + StoreProtocol = "Store" + LegacyStoreProtocol = "Legacy Store" + FilterProtocol = "Filter" + LightpushProtocol = "Lightpush" + LegacyLightpushProtocol = "Legacy Lightpush" + PeerExchangeProtocol = "Peer Exchange" + RendezvousProtocol = "Rendezvous" + MixProtocol = "Mix" + StoreClientProtocol = "Store Client" + LegacyStoreClientProtocol = "Legacy Store Client" + FilterClientProtocol = "Filter Client" + LightpushClientProtocol = "Lightpush Client" + LegacyLightpushClientProtocol = "Legacy Lightpush Client" + +const + RelayProtocols* = {RelayProtocol} + StoreClientProtocols* = {StoreClientProtocol, LegacyStoreClientProtocol} + LightpushClientProtocols* = {LightpushClientProtocol, LegacyLightpushClientProtocol} + FilterClientProtocols* = {FilterClientProtocol} diff --git a/waku/events/events.nim b/waku/events/events.nim index 2a0af8828..46dd4fdd3 100644 --- a/waku/events/events.nim +++ b/waku/events/events.nim @@ -1,3 +1,3 @@ -import ./[message_events, delivery_events] +import ./[message_events, delivery_events, health_events, peer_events] -export message_events, delivery_events +export message_events, delivery_events, health_events, peer_events diff --git a/waku/events/health_events.nim b/waku/events/health_events.nim new file mode 100644 index 000000000..1e6decedb --- /dev/null +++ b/waku/events/health_events.nim @@ -0,0 +1,27 @@ +import waku/common/broker/event_broker + +import waku/api/types +import waku/node/health_monitor/[protocol_health, topic_health] +import waku/waku_core/topics + +export protocol_health, topic_health + +# Notify health changes to node connectivity +EventBroker: + type EventConnectionStatusChange* = object + connectionStatus*: ConnectionStatus + +# Notify health changes to a subscribed topic +# TODO: emit content topic health change events when subscribe/unsubscribe +# from/to content topic is provided in the new API (so we know which +# content topics are of interest to the application) +EventBroker: + type EventContentTopicHealthChange* = object + contentTopic*: ContentTopic + health*: TopicHealth + +# Notify health changes to a shard (pubsub topic) +EventBroker: + type EventShardTopicHealthChange* = object + topic*: PubsubTopic + health*: TopicHealth diff --git a/waku/events/peer_events.nim b/waku/events/peer_events.nim new file mode 100644 index 000000000..49dfa9f9a --- /dev/null +++ b/waku/events/peer_events.nim @@ -0,0 +1,13 @@ +import waku/common/broker/event_broker +import libp2p/switch + +type WakuPeerEventKind* {.pure.} = enum + EventConnected + EventDisconnected + EventIdentified + EventMetadataUpdated + +EventBroker: + type EventWakuPeer* = object + peerId*: PeerId + kind*: WakuPeerEventKind diff --git a/waku/factory/app_callbacks.nim b/waku/factory/app_callbacks.nim index d28b9f2d1..f1d3369be 100644 --- a/waku/factory/app_callbacks.nim +++ b/waku/factory/app_callbacks.nim @@ -1,6 +1,7 @@ -import ../waku_relay, ../node/peer_manager +import ../waku_relay, ../node/peer_manager, ../node/health_monitor/connection_status type AppCallbacks* = ref object relayHandler*: WakuRelayHandler topicHealthChangeHandler*: TopicHealthChangeHandler connectionChangeHandler*: ConnectionChangeHandler + connectionStatusChangeHandler*: ConnectionStatusChangeHandler diff --git a/waku/factory/builder.nim b/waku/factory/builder.nim index f379f92bb..e0b643fc0 100644 --- a/waku/factory/builder.nim +++ b/waku/factory/builder.nim @@ -15,7 +15,8 @@ import ../waku_node, ../node/peer_manager, ../common/rate_limit/setting, - ../common/utils/parse_size_units + ../common/utils/parse_size_units, + ../common/broker/broker_context type WakuNodeBuilder* = object # General diff --git a/waku/factory/waku.nim b/waku/factory/waku.nim index 3748847f1..dd253129c 100644 --- a/waku/factory/waku.nim +++ b/waku/factory/waku.nim @@ -17,35 +17,36 @@ import eth/p2p/discoveryv5/enr, presto, metrics, - metrics/chronos_httpserver -import - ../common/logging, - ../waku_core, - ../waku_node, - ../node/peer_manager, - ../node/health_monitor, - ../node/waku_metrics, - ../node/delivery_service/delivery_service, - ../rest_api/message_cache, - ../rest_api/endpoint/server, - ../rest_api/endpoint/builder as rest_server_builder, - ../waku_archive, - ../waku_relay/protocol, - ../discovery/waku_dnsdisc, - ../discovery/waku_discv5, - ../discovery/autonat_service, - ../waku_enr/sharding, - ../waku_rln_relay, - ../waku_store, - ../waku_filter_v2, - ../factory/node_factory, - ../factory/internal_config, - ../factory/app_callbacks, - ../waku_enr/multiaddr, - ./waku_conf, - ../common/broker/broker_context, - ../requests/health_request, - ../api/types + metrics/chronos_httpserver, + waku/[ + waku_core, + waku_node, + waku_archive, + waku_rln_relay, + waku_store, + waku_filter_v2, + waku_relay/protocol, + waku_enr/sharding, + waku_enr/multiaddr, + api/types, + common/logging, + common/broker/broker_context, + node/peer_manager, + node/health_monitor, + node/waku_metrics, + node/delivery_service/delivery_service, + rest_api/message_cache, + rest_api/endpoint/server, + rest_api/endpoint/builder as rest_server_builder, + discovery/waku_dnsdisc, + discovery/waku_discv5, + discovery/autonat_service, + requests/health_requests, + factory/node_factory, + factory/internal_config, + factory/app_callbacks, + ], + ./waku_conf logScope: topics = "wakunode waku" @@ -118,7 +119,10 @@ proc newCircuitRelay(isRelayClient: bool): Relay = return Relay.new() proc setupAppCallbacks( - node: WakuNode, conf: WakuConf, appCallbacks: AppCallbacks + node: WakuNode, + conf: WakuConf, + appCallbacks: AppCallbacks, + healthMonitor: NodeHealthMonitor, ): Result[void, string] = if appCallbacks.isNil(): info "No external callbacks to be set" @@ -159,6 +163,13 @@ proc setupAppCallbacks( err("Cannot configure connectionChangeHandler callback with empty peer manager") node.peerManager.onConnectionChange = appCallbacks.connectionChangeHandler + if not appCallbacks.connectionStatusChangeHandler.isNil(): + if healthMonitor.isNil(): + return + err("Cannot configure connectionStatusChangeHandler with empty health monitor") + + healthMonitor.onConnectionStatusChange = appCallbacks.connectionStatusChangeHandler + return ok() proc new*( @@ -192,7 +203,7 @@ proc new*( else: nil - node.setupAppCallbacks(wakuConf, appCallbacks).isOkOr: + node.setupAppCallbacks(wakuConf, appCallbacks, healthMonitor).isOkOr: error "Failed setting up app callbacks", error = error return err("Failed setting up app callbacks: " & $error) @@ -409,60 +420,48 @@ proc startWaku*(waku: ptr Waku): Future[Result[void, string]] {.async: (raises: waku[].healthMonitor.startHealthMonitor().isOkOr: return err("failed to start health monitor: " & $error) - ## Setup RequestNodeHealth provider + ## Setup RequestConnectionStatus provider - RequestNodeHealth.setProvider( + RequestConnectionStatus.setProvider( globalBrokerContext(), - proc(): Result[RequestNodeHealth, string] = - let healthReportFut = waku[].healthMonitor.getNodeHealthReport() - if not healthReportFut.completed(): - return err("Health report not available") + proc(): Result[RequestConnectionStatus, string] = try: - let healthReport = healthReportFut.read() - - # Check if Relay or Lightpush Client is ready (MinimallyHealthy condition) - var relayReady = false - var lightpushClientReady = false - var storeClientReady = false - var filterClientReady = false - - for protocolHealth in healthReport.protocolsHealth: - if protocolHealth.protocol == "Relay" and - protocolHealth.health == HealthStatus.READY: - relayReady = true - elif protocolHealth.protocol == "Lightpush Client" and - protocolHealth.health == HealthStatus.READY: - lightpushClientReady = true - elif protocolHealth.protocol == "Store Client" and - protocolHealth.health == HealthStatus.READY: - storeClientReady = true - elif protocolHealth.protocol == "Filter Client" and - protocolHealth.health == HealthStatus.READY: - filterClientReady = true - - # Determine node health based on protocol states - let isMinimallyHealthy = relayReady or lightpushClientReady - let nodeHealth = - if isMinimallyHealthy and storeClientReady and filterClientReady: - NodeHealth.Healthy - elif isMinimallyHealthy: - NodeHealth.MinimallyHealthy - else: - NodeHealth.Unhealthy - - debug "Providing health report", - nodeHealth = $nodeHealth, - relayReady = relayReady, - lightpushClientReady = lightpushClientReady, - storeClientReady = storeClientReady, - filterClientReady = filterClientReady, - details = $(healthReport) - - return ok(RequestNodeHealth(healthStatus: nodeHealth)) - except CatchableError as exc: - err("Failed to read health report: " & exc.msg), + let healthReport = waku[].healthMonitor.getSyncNodeHealthReport() + return + ok(RequestConnectionStatus(connectionStatus: healthReport.connectionStatus)) + except CatchableError: + err("Failed to read health report: " & getCurrentExceptionMsg()), ).isOkOr: - error "Failed to set RequestNodeHealth provider", error = error + error "Failed to set RequestConnectionStatus provider", error = error + + ## Setup RequestProtocolHealth provider + + RequestProtocolHealth.setProvider( + globalBrokerContext(), + proc( + protocol: WakuProtocol + ): Future[Result[RequestProtocolHealth, string]] {.async.} = + try: + let protocolHealthStatus = + await waku[].healthMonitor.getProtocolHealthInfo(protocol) + return ok(RequestProtocolHealth(healthStatus: protocolHealthStatus)) + except CatchableError: + return err("Failed to get protocol health: " & getCurrentExceptionMsg()), + ).isOkOr: + error "Failed to set RequestProtocolHealth provider", error = error + + ## Setup RequestHealthReport provider (The lost child) + + RequestHealthReport.setProvider( + globalBrokerContext(), + proc(): Future[Result[RequestHealthReport, string]] {.async.} = + try: + let report = await waku[].healthMonitor.getNodeHealthReport() + return ok(RequestHealthReport(healthReport: report)) + except CatchableError: + return err("Failed to get health report: " & getCurrentExceptionMsg()), + ).isOkOr: + error "Failed to set RequestHealthReport provider", error = error if conf.restServerConf.isSome(): rest_server_builder.startRestServerProtocolSupport( @@ -521,8 +520,8 @@ proc stop*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = if not waku.healthMonitor.isNil(): await waku.healthMonitor.stopHealthMonitor() - ## Clear RequestNodeHealth provider - RequestNodeHealth.clearProvider(waku.brokerCtx) + ## Clear RequestConnectionStatus provider + RequestConnectionStatus.clearProvider(waku.brokerCtx) if not waku.restServer.isNil(): await waku.restServer.stop() diff --git a/waku/node/delivery_service/send_service/relay_processor.nim b/waku/node/delivery_service/send_service/relay_processor.nim index 974c22f6c..833d15845 100644 --- a/waku/node/delivery_service/send_service/relay_processor.nim +++ b/waku/node/delivery_service/send_service/relay_processor.nim @@ -1,7 +1,7 @@ import std/options import chronos, chronicles import waku/[waku_core], waku/waku_lightpush/[common, rpc] -import waku/requests/health_request +import waku/requests/health_requests import waku/common/broker/broker_context import waku/api/types import ./[delivery_task, send_processor] @@ -32,7 +32,7 @@ proc new*( ) proc isTopicHealthy(self: RelaySendProcessor, topic: PubsubTopic): bool {.gcsafe.} = - let healthReport = RequestRelayTopicsHealth.request(self.brokerCtx, @[topic]).valueOr: + let healthReport = RequestShardTopicsHealth.request(self.brokerCtx, @[topic]).valueOr: error "isTopicHealthy: failed to get health report", topic = topic, error = error return false diff --git a/waku/node/health_monitor.nim b/waku/node/health_monitor.nim index 854a8bbc0..6e42352d4 100644 --- a/waku/node/health_monitor.nim +++ b/waku/node/health_monitor.nim @@ -1,4 +1,9 @@ import - health_monitor/[node_health_monitor, protocol_health, online_monitor, health_status] + health_monitor/[ + node_health_monitor, protocol_health, online_monitor, health_status, + connection_status, health_report, + ] -export node_health_monitor, protocol_health, online_monitor, health_status +export + node_health_monitor, protocol_health, online_monitor, health_status, + connection_status, health_report diff --git a/waku/node/health_monitor/connection_status.nim b/waku/node/health_monitor/connection_status.nim new file mode 100644 index 000000000..77696130a --- /dev/null +++ b/waku/node/health_monitor/connection_status.nim @@ -0,0 +1,15 @@ +import chronos, results, std/strutils, ../../api/types + +export ConnectionStatus + +proc init*( + t: typedesc[ConnectionStatus], strRep: string +): Result[ConnectionStatus, string] = + try: + let status = parseEnum[ConnectionStatus](strRep) + return ok(status) + except ValueError: + return err("Invalid ConnectionStatus string representation: " & strRep) + +type ConnectionStatusChangeHandler* = + proc(status: ConnectionStatus): Future[void] {.gcsafe, raises: [Defect].} diff --git a/waku/node/health_monitor/health_report.nim b/waku/node/health_monitor/health_report.nim new file mode 100644 index 000000000..d6c23cd28 --- /dev/null +++ b/waku/node/health_monitor/health_report.nim @@ -0,0 +1,10 @@ +{.push raises: [].} + +import ./health_status, ./connection_status, ./protocol_health + +type HealthReport* = object + ## Rest API type returned for /health endpoint + ## + nodeHealth*: HealthStatus # legacy "READY" health indicator + connectionStatus*: ConnectionStatus # new "Connected" health indicator + protocolsHealth*: seq[ProtocolHealth] diff --git a/waku/node/health_monitor/node_health_monitor.nim b/waku/node/health_monitor/node_health_monitor.nim index 4b13dfd3d..ba0518e61 100644 --- a/waku/node/health_monitor/node_health_monitor.nim +++ b/waku/node/health_monitor/node_health_monitor.nim @@ -1,55 +1,89 @@ {.push raises: [].} import - std/[options, sets, random, sequtils], + std/[options, sets, random, sequtils, json, strutils, tables], chronos, chronicles, - libp2p/protocols/rendezvous - -import - ../waku_node, - ../kernel_api, - ../../waku_rln_relay, - ../../waku_relay, - ../peer_manager, - ./online_monitor, - ./health_status, - ./protocol_health + libp2p/protocols/rendezvous, + libp2p/protocols/pubsub, + libp2p/protocols/pubsub/rpc/messages, + waku/[ + waku_relay, + waku_rln_relay, + api/types, + events/health_events, + events/peer_events, + node/waku_node, + node/peer_manager, + node/kernel_api, + node/health_monitor/online_monitor, + node/health_monitor/health_status, + node/health_monitor/health_report, + node/health_monitor/connection_status, + node/health_monitor/protocol_health, + ] ## This module is aimed to check the state of the "self" Waku Node # randomize initializes sdt/random's random number generator # if not called, the outcome of randomization procedures will be the same in every run -randomize() +random.randomize() -type - HealthReport* = object - nodeHealth*: HealthStatus - protocolsHealth*: seq[ProtocolHealth] +const HealthyThreshold* = 2 + ## minimum peers required for all services for a Connected status, excluding Relay - NodeHealthMonitor* = ref object - nodeHealth: HealthStatus - node: WakuNode - onlineMonitor*: OnlineMonitor - keepAliveFut: Future[void] +type NodeHealthMonitor* = ref object + nodeHealth: HealthStatus + node: WakuNode + onlineMonitor*: OnlineMonitor + keepAliveFut: Future[void] + healthLoopFut: Future[void] + healthUpdateEvent: AsyncEvent + connectionStatus: ConnectionStatus + onConnectionStatusChange*: ConnectionStatusChangeHandler + cachedProtocols: seq[ProtocolHealth] + ## state of each protocol to report. + ## calculated on last event that can change any protocol's state so fetching a report is fast. + strength: Table[WakuProtocol, int] + ## latest known connectivity strength (e.g. connected peer count) metric for each protocol. + ## if it doesn't make sense for the protocol in question, this is set to zero. + relayObserver: PubSubObserver + peerEventListener: EventWakuPeerListener + +func getHealth*(report: HealthReport, kind: WakuProtocol): ProtocolHealth = + for h in report.protocolsHealth: + if h.protocol == $kind: + return h + # Shouldn't happen, but if it does, then assume protocol is not mounted + return ProtocolHealth.init(kind) + +proc countCapablePeers(hm: NodeHealthMonitor, codec: string): int = + if isNil(hm.node.peerManager): + return 0 + + return hm.node.peerManager.getCapablePeersCount(codec) proc getRelayHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Relay") + var p = ProtocolHealth.init(WakuProtocol.RelayProtocol) - if hm.node.wakuRelay == nil: + if isNil(hm.node.wakuRelay): + hm.strength[WakuProtocol.RelayProtocol] = 0 return p.notMounted() let relayPeers = hm.node.wakuRelay.getConnectedPubSubPeers(pubsubTopic = "").valueOr: + hm.strength[WakuProtocol.RelayProtocol] = 0 return p.notMounted() - if relayPeers.len() == 0: + let count = relayPeers.len + hm.strength[WakuProtocol.RelayProtocol] = count + if count == 0: return p.notReady("No connected peers") return p.ready() proc getRlnRelayHealth(hm: NodeHealthMonitor): Future[ProtocolHealth] {.async.} = - var p = ProtocolHealth.init("Rln Relay") - if hm.node.wakuRlnRelay.isNil(): + var p = ProtocolHealth.init(WakuProtocol.RlnRelayProtocol) + if isNil(hm.node.wakuRlnRelay): return p.notMounted() const FutIsReadyTimout = 5.seconds @@ -72,121 +106,144 @@ proc getRlnRelayHealth(hm: NodeHealthMonitor): Future[ProtocolHealth] {.async.} proc getLightpushHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = - var p = ProtocolHealth.init("Lightpush") + var p = ProtocolHealth.init(WakuProtocol.LightpushProtocol) - if hm.node.wakuLightPush == nil: + if isNil(hm.node.wakuLightPush): + hm.strength[WakuProtocol.LightpushProtocol] = 0 return p.notMounted() + let peerCount = countCapablePeers(hm, WakuLightPushCodec) + hm.strength[WakuProtocol.LightpushProtocol] = peerCount + if relayHealth == HealthStatus.READY: return p.ready() return p.notReady("Node has no relay peers to fullfill push requests") -proc getLightpushClientHealth( - hm: NodeHealthMonitor, relayHealth: HealthStatus -): ProtocolHealth = - var p = ProtocolHealth.init("Lightpush Client") - - if hm.node.wakuLightpushClient == nil: - return p.notMounted() - - let selfServiceAvailable = - hm.node.wakuLightPush != nil and relayHealth == HealthStatus.READY - let servicePeerAvailable = hm.node.peerManager.selectPeer(WakuLightPushCodec).isSome() - - if selfServiceAvailable or servicePeerAvailable: - return p.ready() - - return p.notReady("No Lightpush service peer available yet") - proc getLegacyLightpushHealth( hm: NodeHealthMonitor, relayHealth: HealthStatus ): ProtocolHealth = - var p = ProtocolHealth.init("Legacy Lightpush") + var p = ProtocolHealth.init(WakuProtocol.LegacyLightpushProtocol) - if hm.node.wakuLegacyLightPush == nil: + if isNil(hm.node.wakuLegacyLightPush): + hm.strength[WakuProtocol.LegacyLightpushProtocol] = 0 return p.notMounted() + let peerCount = countCapablePeers(hm, WakuLegacyLightPushCodec) + hm.strength[WakuProtocol.LegacyLightpushProtocol] = peerCount + if relayHealth == HealthStatus.READY: return p.ready() return p.notReady("Node has no relay peers to fullfill push requests") -proc getLegacyLightpushClientHealth( - hm: NodeHealthMonitor, relayHealth: HealthStatus -): ProtocolHealth = - var p = ProtocolHealth.init("Legacy Lightpush Client") - - if hm.node.wakuLegacyLightpushClient == nil: - return p.notMounted() - - if (hm.node.wakuLegacyLightPush != nil and relayHealth == HealthStatus.READY) or - hm.node.peerManager.selectPeer(WakuLegacyLightPushCodec).isSome(): - return p.ready() - - return p.notReady("No Lightpush service peer available yet") - proc getFilterHealth(hm: NodeHealthMonitor, relayHealth: HealthStatus): ProtocolHealth = - var p = ProtocolHealth.init("Filter") + var p = ProtocolHealth.init(WakuProtocol.FilterProtocol) - if hm.node.wakuFilter == nil: + if isNil(hm.node.wakuFilter): + hm.strength[WakuProtocol.FilterProtocol] = 0 return p.notMounted() + let peerCount = countCapablePeers(hm, WakuFilterSubscribeCodec) + hm.strength[WakuProtocol.FilterProtocol] = peerCount + if relayHealth == HealthStatus.READY: return p.ready() return p.notReady("Relay is not ready, filter will not be able to sort out messages") -proc getFilterClientHealth( - hm: NodeHealthMonitor, relayHealth: HealthStatus -): ProtocolHealth = - var p = ProtocolHealth.init("Filter Client") - - if hm.node.wakuFilterClient == nil: - return p.notMounted() - - if hm.node.peerManager.selectPeer(WakuFilterSubscribeCodec).isSome(): - return p.ready() - - return p.notReady("No Filter service peer available yet") - proc getStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Store") + var p = ProtocolHealth.init(WakuProtocol.StoreProtocol) - if hm.node.wakuStore == nil: + if isNil(hm.node.wakuStore): + hm.strength[WakuProtocol.StoreProtocol] = 0 return p.notMounted() + let peerCount = countCapablePeers(hm, WakuStoreCodec) + hm.strength[WakuProtocol.StoreProtocol] = peerCount return p.ready() -proc getStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Store Client") +proc getLegacyStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = + var p = ProtocolHealth.init(WakuProtocol.LegacyStoreProtocol) - if hm.node.wakuStoreClient == nil: + if isNil(hm.node.wakuLegacyStore): + hm.strength[WakuProtocol.LegacyStoreProtocol] = 0 return p.notMounted() - if hm.node.peerManager.selectPeer(WakuStoreCodec).isSome() or hm.node.wakuStore != nil: + let peerCount = hm.countCapablePeers(WakuLegacyStoreCodec) + hm.strength[WakuProtocol.LegacyStoreProtocol] = peerCount + return p.ready() + +proc getLightpushClientHealth(hm: NodeHealthMonitor): ProtocolHealth = + var p = ProtocolHealth.init(WakuProtocol.LightpushClientProtocol) + + if isNil(hm.node.wakuLightpushClient): + hm.strength[WakuProtocol.LightpushClientProtocol] = 0 + return p.notMounted() + + let peerCount = countCapablePeers(hm, WakuLightPushCodec) + hm.strength[WakuProtocol.LightpushClientProtocol] = peerCount + + if peerCount > 0: + return p.ready() + return p.notReady("No Lightpush service peer available yet") + +proc getLegacyLightpushClientHealth(hm: NodeHealthMonitor): ProtocolHealth = + var p = ProtocolHealth.init(WakuProtocol.LegacyLightpushClientProtocol) + + if isNil(hm.node.wakuLegacyLightpushClient): + hm.strength[WakuProtocol.LegacyLightpushClientProtocol] = 0 + return p.notMounted() + + let peerCount = countCapablePeers(hm, WakuLegacyLightPushCodec) + hm.strength[WakuProtocol.LegacyLightpushClientProtocol] = peerCount + + if peerCount > 0: + return p.ready() + return p.notReady("No Lightpush service peer available yet") + +proc getFilterClientHealth(hm: NodeHealthMonitor): ProtocolHealth = + var p = ProtocolHealth.init(WakuProtocol.FilterClientProtocol) + + if isNil(hm.node.wakuFilterClient): + hm.strength[WakuProtocol.FilterClientProtocol] = 0 + return p.notMounted() + + let peerCount = countCapablePeers(hm, WakuFilterSubscribeCodec) + hm.strength[WakuProtocol.FilterClientProtocol] = peerCount + + if peerCount > 0: + return p.ready() + return p.notReady("No Filter service peer available yet") + +proc getStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = + var p = ProtocolHealth.init(WakuProtocol.StoreClientProtocol) + + if isNil(hm.node.wakuStoreClient): + hm.strength[WakuProtocol.StoreClientProtocol] = 0 + return p.notMounted() + + let peerCount = countCapablePeers(hm, WakuStoreCodec) + hm.strength[WakuProtocol.StoreClientProtocol] = peerCount + + if peerCount > 0 or not isNil(hm.node.wakuStore): return p.ready() return p.notReady( "No Store service peer available yet, neither Store service set up for the node" ) -proc getLegacyStoreHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Legacy Store") - - if hm.node.wakuLegacyStore == nil: - return p.notMounted() - - return p.ready() - proc getLegacyStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Legacy Store Client") + var p = ProtocolHealth.init(WakuProtocol.LegacyStoreClientProtocol) - if hm.node.wakuLegacyStoreClient == nil: + if isNil(hm.node.wakuLegacyStoreClient): + hm.strength[WakuProtocol.LegacyStoreClientProtocol] = 0 return p.notMounted() - if hm.node.peerManager.selectPeer(WakuLegacyStoreCodec).isSome() or - hm.node.wakuLegacyStore != nil: + let peerCount = countCapablePeers(hm, WakuLegacyStoreCodec) + hm.strength[WakuProtocol.LegacyStoreClientProtocol] = peerCount + + if peerCount > 0 or not isNil(hm.node.wakuLegacyStore): return p.ready() return p.notReady( @@ -194,38 +251,305 @@ proc getLegacyStoreClientHealth(hm: NodeHealthMonitor): ProtocolHealth = ) proc getPeerExchangeHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Peer Exchange") + var p = ProtocolHealth.init(WakuProtocol.PeerExchangeProtocol) - if hm.node.wakuPeerExchange == nil: + if isNil(hm.node.wakuPeerExchange): + hm.strength[WakuProtocol.PeerExchangeProtocol] = 0 return p.notMounted() + let peerCount = countCapablePeers(hm, WakuPeerExchangeCodec) + hm.strength[WakuProtocol.PeerExchangeProtocol] = peerCount + return p.ready() proc getRendezvousHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Rendezvous") + var p = ProtocolHealth.init(WakuProtocol.RendezvousProtocol) - if hm.node.wakuRendezvous == nil: + if isNil(hm.node.wakuRendezvous): + hm.strength[WakuProtocol.RendezvousProtocol] = 0 return p.notMounted() - if hm.node.peerManager.switch.peerStore.peers(RendezVousCodec).len() == 0: + let peerCount = countCapablePeers(hm, RendezVousCodec) + hm.strength[WakuProtocol.RendezvousProtocol] = peerCount + if peerCount == 0: return p.notReady("No Rendezvous peers are available yet") return p.ready() proc getMixHealth(hm: NodeHealthMonitor): ProtocolHealth = - var p = ProtocolHealth.init("Mix") + var p = ProtocolHealth.init(WakuProtocol.MixProtocol) - if hm.node.wakuMix.isNil(): + if isNil(hm.node.wakuMix): return p.notMounted() return p.ready() +proc getSyncProtocolHealthInfo*( + hm: NodeHealthMonitor, protocol: WakuProtocol +): ProtocolHealth = + ## Get ProtocolHealth for a given protocol that can provide it synchronously + ## + case protocol + of WakuProtocol.RelayProtocol: + return hm.getRelayHealth() + of WakuProtocol.StoreProtocol: + return hm.getStoreHealth() + of WakuProtocol.LegacyStoreProtocol: + return hm.getLegacyStoreHealth() + of WakuProtocol.FilterProtocol: + return hm.getFilterHealth(hm.getRelayHealth().health) + of WakuProtocol.LightpushProtocol: + return hm.getLightpushHealth(hm.getRelayHealth().health) + of WakuProtocol.LegacyLightpushProtocol: + return hm.getLegacyLightpushHealth(hm.getRelayHealth().health) + of WakuProtocol.PeerExchangeProtocol: + return hm.getPeerExchangeHealth() + of WakuProtocol.RendezvousProtocol: + return hm.getRendezvousHealth() + of WakuProtocol.MixProtocol: + return hm.getMixHealth() + of WakuProtocol.StoreClientProtocol: + return hm.getStoreClientHealth() + of WakuProtocol.LegacyStoreClientProtocol: + return hm.getLegacyStoreClientHealth() + of WakuProtocol.FilterClientProtocol: + return hm.getFilterClientHealth() + of WakuProtocol.LightpushClientProtocol: + return hm.getLightpushClientHealth() + of WakuProtocol.LegacyLightpushClientProtocol: + return hm.getLegacyLightpushClientHealth() + of WakuProtocol.RlnRelayProtocol: + # Could waitFor here but we don't want to block the main thread. + # Could also return a cached value from a previous check. + var p = ProtocolHealth.init(protocol) + return p.notReady("RLN Relay health check is async") + else: + var p = ProtocolHealth.init(protocol) + return p.notMounted() + +proc getProtocolHealthInfo*( + hm: NodeHealthMonitor, protocol: WakuProtocol +): Future[ProtocolHealth] {.async.} = + ## Get ProtocolHealth for a given protocol + ## + case protocol + of WakuProtocol.RlnRelayProtocol: + return await hm.getRlnRelayHealth() + else: + return hm.getSyncProtocolHealthInfo(protocol) + +proc getSyncAllProtocolHealthInfo(hm: NodeHealthMonitor): seq[ProtocolHealth] = + ## Get ProtocolHealth for the subset of protocols that can provide it synchronously + ## + var protocols: seq[ProtocolHealth] = @[] + let relayHealth = hm.getRelayHealth() + protocols.add(relayHealth) + + protocols.add(hm.getLightpushHealth(relayHealth.health)) + protocols.add(hm.getLegacyLightpushHealth(relayHealth.health)) + protocols.add(hm.getFilterHealth(relayHealth.health)) + protocols.add(hm.getStoreHealth()) + protocols.add(hm.getLegacyStoreHealth()) + protocols.add(hm.getPeerExchangeHealth()) + protocols.add(hm.getRendezvousHealth()) + protocols.add(hm.getMixHealth()) + + protocols.add(hm.getLightpushClientHealth()) + protocols.add(hm.getLegacyLightpushClientHealth()) + protocols.add(hm.getStoreClientHealth()) + protocols.add(hm.getLegacyStoreClientHealth()) + protocols.add(hm.getFilterClientHealth()) + return protocols + +proc getAllProtocolHealthInfo( + hm: NodeHealthMonitor +): Future[seq[ProtocolHealth]] {.async.} = + ## Get ProtocolHealth for all protocols + ## + var protocols = hm.getSyncAllProtocolHealthInfo() + + let rlnHealth = await hm.getRlnRelayHealth() + protocols.add(rlnHealth) + + return protocols + +proc calculateConnectionState*( + protocols: seq[ProtocolHealth], + strength: Table[WakuProtocol, int], ## latest connectivity strength (e.g. peer count) for a protocol + dLowOpt: Option[int], ## minimum relay peers for Connected status if in Core (Relay) mode +): ConnectionStatus = + var + relayCount = 0 + lightpushCount = 0 + filterCount = 0 + storeClientCount = 0 + + for p in protocols: + let kind = + try: + parseEnum[WakuProtocol](p.protocol) + except ValueError: + continue + + if p.health != HealthStatus.READY: + continue + + let strength = strength.getOrDefault(kind, 0) + + if kind in RelayProtocols: + relayCount = max(relayCount, strength) + elif kind in StoreClientProtocols: + storeClientCount = max(storeClientCount, strength) + elif kind in LightpushClientProtocols: + lightpushCount = max(lightpushCount, strength) + elif kind in FilterClientProtocols: + filterCount = max(filterCount, strength) + + debug "calculateConnectionState", + protocol = kind, + strength = strength, + relayCount = relayCount, + storeClientCount = storeClientCount, + lightpushCount = lightpushCount, + filterCount = filterCount + + # Relay connectivity should be a sufficient check in Core mode. + # "Store peers" are relay peers because incoming messages in + # the relay are input to the store server. + # But if Store server (or client, even) is not mounted as well, this logic assumes + # the user knows what they're doing. + + if dLowOpt.isSome(): + if relayCount >= dLowOpt.get(): + return ConnectionStatus.Connected + + if relayCount > 0: + return ConnectionStatus.PartiallyConnected + + # No relay connectivity. Relay might not be mounted, or may just have zero peers. + # Fall back to Edge check in any case to be sure. + + let canSend = lightpushCount > 0 + let canReceive = filterCount > 0 + let canStore = storeClientCount > 0 + + let meetsMinimum = canSend and canReceive and canStore + + if not meetsMinimum: + return ConnectionStatus.Disconnected + + let isEdgeRobust = + (lightpushCount >= HealthyThreshold) and (filterCount >= HealthyThreshold) and + (storeClientCount >= HealthyThreshold) + + if isEdgeRobust: + return ConnectionStatus.Connected + + return ConnectionStatus.PartiallyConnected + +proc calculateConnectionState*(hm: NodeHealthMonitor): ConnectionStatus = + let dLow = + if isNil(hm.node.wakuRelay): + none(int) + else: + some(hm.node.wakuRelay.parameters.dLow) + return calculateConnectionState(hm.cachedProtocols, hm.strength, dLow) + +proc getNodeHealthReport*(hm: NodeHealthMonitor): Future[HealthReport] {.async.} = + ## Get a HealthReport that includes all protocols + ## + var report: HealthReport + + if hm.nodeHealth == HealthStatus.INITIALIZING or + hm.nodeHealth == HealthStatus.SHUTTING_DOWN: + report.nodeHealth = hm.nodeHealth + report.connectionStatus = ConnectionStatus.Disconnected + return report + + if hm.cachedProtocols.len == 0: + hm.cachedProtocols = await hm.getAllProtocolHealthInfo() + hm.connectionStatus = hm.calculateConnectionState() + + report.nodeHealth = HealthStatus.READY + report.connectionStatus = hm.connectionStatus + report.protocolsHealth = hm.cachedProtocols + return report + +proc getSyncNodeHealthReport*(hm: NodeHealthMonitor): HealthReport = + ## Get a HealthReport that includes the subset of protocols that inform health synchronously + ## + var report: HealthReport + + if hm.nodeHealth == HealthStatus.INITIALIZING or + hm.nodeHealth == HealthStatus.SHUTTING_DOWN: + report.nodeHealth = hm.nodeHealth + report.connectionStatus = ConnectionStatus.Disconnected + return report + + if hm.cachedProtocols.len == 0: + hm.cachedProtocols = hm.getSyncAllProtocolHealthInfo() + hm.connectionStatus = hm.calculateConnectionState() + + report.nodeHealth = HealthStatus.READY + report.connectionStatus = hm.connectionStatus + report.protocolsHealth = hm.cachedProtocols + return report + +proc onRelayMsg( + hm: NodeHealthMonitor, peer: PubSubPeer, msg: var RPCMsg +) {.gcsafe, raises: [].} = + ## Inspect Relay events for health-update relevance in Core (Relay) mode. + ## + ## For Core (Relay) mode, the connectivity health state is mostly determined + ## by the relay protocol state (it is the dominant factor), and we know + ## that a peer Relay can only affect this Relay's health if there is a + ## subscription change or a mesh (GRAFT/PRUNE) change. + ## + + if msg.subscriptions.len == 0: + if msg.control.isNone(): + return + let ctrl = msg.control.get() + if ctrl.graft.len == 0 and ctrl.prune.len == 0: + return + + hm.healthUpdateEvent.fire() + +proc healthLoop(hm: NodeHealthMonitor) {.async.} = + ## Re-evaluate the global health state of the node when notified of a potential change, + ## and call back the application if an actual change from the last notified state happened. + info "Health monitor loop start" + while true: + try: + await hm.healthUpdateEvent.wait() + hm.healthUpdateEvent.clear() + + hm.cachedProtocols = await hm.getAllProtocolHealthInfo() + let newConnectionStatus = hm.calculateConnectionState() + + if newConnectionStatus != hm.connectionStatus: + hm.connectionStatus = newConnectionStatus + + EventConnectionStatusChange.emit(hm.node.brokerCtx, newConnectionStatus) + + if not isNil(hm.onConnectionStatusChange): + await hm.onConnectionStatusChange(newConnectionStatus) + except CancelledError: + break + except Exception as e: + error "HealthMonitor: error in update loop", error = e.msg + + # safety cooldown to protect from edge cases + await sleepAsync(100.milliseconds) + + info "Health monitor loop end" + proc selectRandomPeersForKeepalive( node: WakuNode, outPeers: seq[PeerId], numRandomPeers: int ): Future[seq[PeerId]] {.async.} = ## Select peers for random keepalive, prioritizing mesh peers - if node.wakuRelay.isNil(): + if isNil(node.wakuRelay): return selectRandomPeers(outPeers, numRandomPeers) let meshPeers = node.wakuRelay.getPeersInMesh().valueOr: @@ -359,45 +683,55 @@ proc startKeepalive*( hm.keepAliveFut = hm.node.keepAliveLoop(randomPeersKeepalive, allPeersKeepalive) return ok() -proc getNodeHealthReport*(hm: NodeHealthMonitor): Future[HealthReport] {.async.} = - var report: HealthReport - report.nodeHealth = hm.nodeHealth - - let relayHealth = hm.getRelayHealth() - report.protocolsHealth.add(relayHealth) - report.protocolsHealth.add(await hm.getRlnRelayHealth()) - report.protocolsHealth.add(hm.getLightpushHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getLegacyLightpushHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getFilterHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getStoreHealth()) - report.protocolsHealth.add(hm.getLegacyStoreHealth()) - report.protocolsHealth.add(hm.getPeerExchangeHealth()) - report.protocolsHealth.add(hm.getRendezvousHealth()) - report.protocolsHealth.add(hm.getMixHealth()) - - report.protocolsHealth.add(hm.getLightpushClientHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getLegacyLightpushClientHealth(relayHealth.health)) - report.protocolsHealth.add(hm.getStoreClientHealth()) - report.protocolsHealth.add(hm.getLegacyStoreClientHealth()) - report.protocolsHealth.add(hm.getFilterClientHealth(relayHealth.health)) - return report - proc setOverallHealth*(hm: NodeHealthMonitor, health: HealthStatus) = hm.nodeHealth = health proc startHealthMonitor*(hm: NodeHealthMonitor): Result[void, string] = hm.onlineMonitor.startOnlineMonitor() + + if isNil(hm.node.peerManager): + return err("startHealthMonitor: no node peerManager to monitor") + + if not isNil(hm.node.wakuRelay): + hm.relayObserver = PubSubObserver( + onRecv: proc(peer: PubSubPeer, msgs: var RPCMsg) {.gcsafe, raises: [].} = + hm.onRelayMsg(peer, msgs) + ) + hm.node.wakuRelay.addObserver(hm.relayObserver) + + hm.peerEventListener = EventWakuPeer.listen( + hm.node.brokerCtx, + proc(evt: EventWakuPeer): Future[void] {.async: (raises: []), gcsafe.} = + ## Recompute health on any peer changing anything (join, leave, identify, metadata update) + hm.healthUpdateEvent.fire(), + ).valueOr: + return err("Failed to subscribe to peer events: " & error) + + hm.healthUpdateEvent = newAsyncEvent() + hm.healthUpdateEvent.fire() + + hm.healthLoopFut = hm.healthLoop() + hm.startKeepalive().isOkOr: return err("startHealthMonitor: failed starting keep alive: " & error) return ok() proc stopHealthMonitor*(hm: NodeHealthMonitor) {.async.} = - if not hm.onlineMonitor.isNil(): + if not isNil(hm.onlineMonitor): await hm.onlineMonitor.stopOnlineMonitor() - if not hm.keepAliveFut.isNil(): + if not isNil(hm.keepAliveFut): await hm.keepAliveFut.cancelAndWait() + if not isNil(hm.healthLoopFut): + await hm.healthLoopFut.cancelAndWait() + + if hm.peerEventListener.id != 0: + EventWakuPeer.dropListener(hm.node.brokerCtx, hm.peerEventListener) + + if not isNil(hm.node.wakuRelay) and not isNil(hm.relayObserver): + hm.node.wakuRelay.removeObserver(hm.relayObserver) + proc new*( T: type NodeHealthMonitor, node: WakuNode, @@ -406,4 +740,10 @@ proc new*( let om = OnlineMonitor.init(dnsNameServers) om.setPeerStoreToOnlineMonitor(node.switch.peerStore) om.addOnlineStateObserver(node.peerManager.getOnlineStateObserver()) - T(nodeHealth: INITIALIZING, node: node, onlineMonitor: om) + T( + nodeHealth: INITIALIZING, + node: node, + onlineMonitor: om, + connectionStatus: ConnectionStatus.Disconnected, + strength: initTable[WakuProtocol, int](), + ) diff --git a/waku/node/health_monitor/protocol_health.nim b/waku/node/health_monitor/protocol_health.nim index 7bacea94b..4479888c8 100644 --- a/waku/node/health_monitor/protocol_health.nim +++ b/waku/node/health_monitor/protocol_health.nim @@ -1,5 +1,8 @@ import std/[options, strformat] import ./health_status +import waku/common/waku_protocol + +export waku_protocol type ProtocolHealth* = object protocol*: string @@ -39,8 +42,7 @@ proc shuttingDown*(p: var ProtocolHealth): ProtocolHealth = proc `$`*(p: ProtocolHealth): string = return fmt"protocol: {p.protocol}, health: {p.health}, description: {p.desc}" -proc init*(p: typedesc[ProtocolHealth], protocol: string): ProtocolHealth = - let p = ProtocolHealth( - protocol: protocol, health: HealthStatus.NOT_MOUNTED, desc: none[string]() +proc init*(p: typedesc[ProtocolHealth], protocol: WakuProtocol): ProtocolHealth = + return ProtocolHealth( + protocol: $protocol, health: HealthStatus.NOT_MOUNTED, desc: none[string]() ) - return p diff --git a/waku/node/peer_manager/peer_manager.nim b/waku/node/peer_manager/peer_manager.nim index bdb68905e..834fb19cf 100644 --- a/waku/node/peer_manager/peer_manager.nim +++ b/waku/node/peer_manager/peer_manager.nim @@ -1,27 +1,31 @@ {.push raises: [].} import - std/[options, sets, sequtils, times, strformat, strutils, math, random, tables], + std/ + [ + options, sets, sequtils, times, strformat, strutils, math, random, tables, + algorithm, + ], chronos, chronicles, metrics, - libp2p/multistream, - libp2p/muxers/muxer, - libp2p/nameresolving/nameresolver, - libp2p/peerstore - -import - ../../common/nimchronos, - ../../common/enr, - ../../common/callbacks, - ../../common/utils/parse_size_units, - ../../waku_core, - ../../waku_relay, - ../../waku_relay/protocol, - ../../waku_enr/sharding, - ../../waku_enr/capabilities, - ../../waku_metadata, - ../health_monitor/online_monitor, + libp2p/[multistream, muxers/muxer, nameresolving/nameresolver, peerstore], + waku/[ + waku_core, + waku_relay, + waku_metadata, + waku_core/topics/sharding, + waku_relay/protocol, + waku_enr/sharding, + waku_enr/capabilities, + events/peer_events, + common/nimchronos, + common/enr, + common/callbacks, + common/utils/parse_size_units, + common/broker/broker_context, + node/health_monitor/online_monitor, + ], ./peer_store/peer_storage, ./waku_peer_store @@ -84,6 +88,7 @@ type ConnectionChangeHandler* = proc( ): Future[void] {.gcsafe, raises: [Defect].} type PeerManager* = ref object of RootObj + brokerCtx: BrokerContext switch*: Switch wakuMetadata*: WakuMetadata initialBackoffInSec*: int @@ -483,8 +488,9 @@ proc canBeConnected*(pm: PeerManager, peerId: PeerId): bool = proc connectedPeers*( pm: PeerManager, protocol: string = "" ): (seq[PeerId], seq[PeerId]) = - ## Returns the peerIds of physical connections (in and out) - ## If a protocol is specified, only returns peers with at least one stream of that protocol + ## Returns the PeerIds of peers with an active socket connection. + ## If a protocol is specified, it returns peers that currently have one + ## or more active logical streams for that protocol. var inPeers: seq[PeerId] var outPeers: seq[PeerId] @@ -500,6 +506,65 @@ proc connectedPeers*( return (inPeers, outPeers) +proc capablePeers*(pm: PeerManager, protocol: string): (seq[PeerId], seq[PeerId]) = + ## Returns the PeerIds of peers with an active socket connection. + ## If a protocol is specified, it returns peers that have identified + ## themselves as supporting the protocol. + + var inPeers: seq[PeerId] + var outPeers: seq[PeerId] + + for peerId, muxers in pm.switch.connManager.getConnections(): + # filter out peers that don't have the capability registered in the peer store + if pm.switch.peerStore.hasPeer(peerId, protocol): + for peerConn in muxers: + if peerConn.connection.transportDir == Direction.In: + inPeers.add(peerId) + elif peerConn.connection.transportDir == Direction.Out: + outPeers.add(peerId) + + return (inPeers, outPeers) + +proc getConnectedPeersCount*(pm: PeerManager, protocol: string): int = + ## Returns the total number of unique connected peers (inbound + outbound) + ## with active streams for a specific protocol. + let (inPeers, outPeers) = pm.connectedPeers(protocol) + var peers = initHashSet[PeerId](nextPowerOfTwo(inPeers.len + outPeers.len)) + for p in inPeers: + peers.incl(p) + for p in outPeers: + peers.incl(p) + return peers.len + +proc getCapablePeersCount*(pm: PeerManager, protocol: string): int = + ## Returns the total number of unique connected peers (inbound + outbound) + ## who have identified themselves as supporting the given protocol. + let (inPeers, outPeers) = pm.capablePeers(protocol) + var peers = initHashSet[PeerId](nextPowerOfTwo(inPeers.len + outPeers.len)) + for p in inPeers: + peers.incl(p) + for p in outPeers: + peers.incl(p) + return peers.len + +proc getPeersForShard*(pm: PeerManager, protocolId: string, shard: PubsubTopic): int = + let (inPeers, outPeers) = pm.connectedPeers(protocolId) + let connectedProtocolPeers = inPeers & outPeers + if connectedProtocolPeers.len == 0: + return 0 + + let shardInfo = RelayShard.parse(shard).valueOr: + # count raw peers of the given protocol if for some reason we can't get + # a shard mapping out of the gossipsub topic string. + return connectedProtocolPeers.len + + var shardPeers = 0 + for peerId in connectedProtocolPeers: + if pm.switch.peerStore.hasShard(peerId, shardInfo.clusterId, shardInfo.shardId): + shardPeers.inc() + + return shardPeers + proc disconnectAllPeers*(pm: PeerManager) {.async.} = let (inPeerIds, outPeerIds) = pm.connectedPeers() let connectedPeers = concat(inPeerIds, outPeerIds) @@ -635,7 +700,7 @@ proc getPeerIp(pm: PeerManager, peerId: PeerId): Option[string] = # Event Handling # #~~~~~~~~~~~~~~~~~# -proc onPeerMetadata(pm: PeerManager, peerId: PeerId) {.async.} = +proc refreshPeerMetadata(pm: PeerManager, peerId: PeerId) {.async.} = let res = catch: await pm.switch.dial(peerId, WakuMetadataCodec) @@ -664,6 +729,10 @@ proc onPeerMetadata(pm: PeerManager, peerId: PeerId) {.async.} = let shards = metadata.shards.mapIt(it.uint16) pm.switch.peerStore.setShardInfo(peerId, shards) + # TODO: should only trigger an event if metadata actually changed + # should include the shard subscription delta in the event when + # it is a MetadataUpdated event + EventWakuPeer.emit(pm.brokerCtx, peerId, WakuPeerEventKind.EventMetadataUpdated) return info "disconnecting from peer", peerId = peerId, reason = reason @@ -673,14 +742,14 @@ proc onPeerMetadata(pm: PeerManager, peerId: PeerId) {.async.} = # called when a peer i) first connects to us ii) disconnects all connections from us proc onPeerEvent(pm: PeerManager, peerId: PeerId, event: PeerEvent) {.async.} = if not pm.wakuMetadata.isNil() and event.kind == PeerEventKind.Joined: - await pm.onPeerMetadata(peerId) + await pm.refreshPeerMetadata(peerId) var peerStore = pm.switch.peerStore var direction: PeerDirection var connectedness: Connectedness case event.kind - of Joined: + of PeerEventKind.Joined: direction = if event.initiator: Outbound else: Inbound connectedness = Connected @@ -708,10 +777,12 @@ proc onPeerEvent(pm: PeerManager, peerId: PeerId, event: PeerEvent) {.async.} = asyncSpawn(pm.switch.disconnect(peerId)) peerStore.delete(peerId) + EventWakuPeer.emit(pm.brokerCtx, peerId, WakuPeerEventKind.EventConnected) + if not pm.onConnectionChange.isNil(): # we don't want to await for the callback to finish asyncSpawn pm.onConnectionChange(peerId, Joined) - of Left: + of PeerEventKind.Left: direction = UnknownDirection connectedness = CanConnect @@ -723,12 +794,16 @@ proc onPeerEvent(pm: PeerManager, peerId: PeerId, event: PeerEvent) {.async.} = pm.ipTable.del(ip) break + EventWakuPeer.emit(pm.brokerCtx, peerId, WakuPeerEventKind.EventDisconnected) + if not pm.onConnectionChange.isNil(): # we don't want to await for the callback to finish asyncSpawn pm.onConnectionChange(peerId, Left) - of Identified: + of PeerEventKind.Identified: info "event identified", peerId = peerId + EventWakuPeer.emit(pm.brokerCtx, peerId, WakuPeerEventKind.EventIdentified) + peerStore[ConnectionBook][peerId] = connectedness peerStore[DirectionBook][peerId] = direction @@ -1085,8 +1160,11 @@ proc new*( error "Max backoff time can't be over 1 week", maxBackoff = backoff raise newException(Defect, "Max backoff time can't be over 1 week") + let brokerCtx = globalBrokerContext() + let pm = PeerManager( switch: switch, + brokerCtx: brokerCtx, wakuMetadata: wakuMetadata, storage: storage, initialBackoffInSec: initialBackoffInSec, diff --git a/waku/node/peer_manager/waku_peer_store.nim b/waku/node/peer_manager/waku_peer_store.nim index b7f2669e5..a03b5ae2e 100644 --- a/waku/node/peer_manager/waku_peer_store.nim +++ b/waku/node/peer_manager/waku_peer_store.nim @@ -162,7 +162,9 @@ proc connectedness*(peerStore: PeerStore, peerId: PeerId): Connectedness = peerStore[ConnectionBook].book.getOrDefault(peerId, NotConnected) proc hasShard*(peerStore: PeerStore, peerId: PeerID, cluster, shard: uint16): bool = - peerStore[ENRBook].book.getOrDefault(peerId).containsShard(cluster, shard) + return + peerStore[ENRBook].book.getOrDefault(peerId).containsShard(cluster, shard) or + peerStore[ShardBook].book.getOrDefault(peerId, @[]).contains(shard) proc hasCapability*(peerStore: PeerStore, peerId: PeerID, cap: Capabilities): bool = peerStore[ENRBook].book.getOrDefault(peerId).supportsCapability(cap) @@ -219,7 +221,8 @@ proc getPeersByShard*( peerStore: PeerStore, cluster, shard: uint16 ): seq[RemotePeerInfo] = return peerStore.peers.filterIt( - it.enr.isSome() and it.enr.get().containsShard(cluster, shard) + (it.enr.isSome() and it.enr.get().containsShard(cluster, shard)) or + it.shards.contains(shard) ) proc getPeersByCapability*( diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index d556811ac..cb3d81c7c 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -42,6 +42,7 @@ import waku_store/resume, waku_store_sync, waku_filter_v2, + waku_filter_v2/common as filter_common, waku_filter_v2/client as filter_client, waku_metadata, waku_rendezvous/protocol, @@ -57,12 +58,18 @@ import common/rate_limit/setting, common/callbacks, common/nimchronos, + common/broker/broker_context, + common/broker/request_broker, waku_mix, requests/node_requests, - common/broker/broker_context, + requests/health_requests, + events/health_events, + events/peer_events, ], ./net_config, - ./peer_manager + ./peer_manager, + ./health_monitor/health_status, + ./health_monitor/topic_health declarePublicCounter waku_node_messages, "number of messages received", ["type"] @@ -91,6 +98,9 @@ const clientId* = "Nimbus Waku v2 node" const WakuNodeVersionString* = "version / git commit hash: " & git_version +const EdgeTopicHealthyThreshold = 2 + ## Lightpush server and filter server requirement for a healthy topic in edge mode + # key and crypto modules different type # TODO: Move to application instance (e.g., `WakuNode2`) @@ -135,6 +145,10 @@ type topicSubscriptionQueue*: AsyncEventQueue[SubscriptionEvent] rateLimitSettings*: ProtocolRateLimitSettings wakuMix*: WakuMix + edgeTopicsHealth*: Table[PubsubTopic, TopicHealth] + edgeHealthEvent*: AsyncEvent + edgeHealthLoop: Future[void] + peerEventListener*: EventWakuPeerListener proc deduceRelayShard( node: WakuNode, @@ -469,7 +483,52 @@ proc updateAnnouncedAddrWithPrimaryIpAddr*(node: WakuNode): Result[void, string] return ok() -proc startProvidersAndListeners(node: WakuNode) = +proc calculateEdgeTopicHealth(node: WakuNode, shard: PubsubTopic): TopicHealth = + let filterPeers = + node.peerManager.getPeersForShard(filter_common.WakuFilterSubscribeCodec, shard) + let lightpushPeers = + node.peerManager.getPeersForShard(lightpush_protocol.WakuLightPushCodec, shard) + + if filterPeers >= EdgeTopicHealthyThreshold and + lightpushPeers >= EdgeTopicHealthyThreshold: + return TopicHealth.SUFFICIENTLY_HEALTHY + elif filterPeers > 0 and lightpushPeers > 0: + return TopicHealth.MINIMALLY_HEALTHY + + return TopicHealth.UNHEALTHY + +proc loopEdgeHealth(node: WakuNode) {.async.} = + while node.started: + await node.edgeHealthEvent.wait() + node.edgeHealthEvent.clear() + + try: + for shard in node.edgeTopicsHealth.keys: + if not node.wakuRelay.isNil and node.wakuRelay.isSubscribed(shard): + continue + + let oldHealth = node.edgeTopicsHealth.getOrDefault(shard, TopicHealth.UNHEALTHY) + let newHealth = node.calculateEdgeTopicHealth(shard) + if newHealth != oldHealth: + node.edgeTopicsHealth[shard] = newHealth + EventShardTopicHealthChange.emit(node.brokerCtx, shard, newHealth) + except CancelledError: + break + except CatchableError as e: + warn "Error in edge health check", error = e.msg + + # safety cooldown to protect from edge cases + await sleepAsync(100.milliseconds) + +proc startProvidersAndListeners*(node: WakuNode) = + node.peerEventListener = EventWakuPeer.listen( + node.brokerCtx, + proc(evt: EventWakuPeer) {.async: (raises: []), gcsafe.} = + node.edgeHealthEvent.fire(), + ).valueOr: + error "Failed to listen to peer events", error = error + return + RequestRelayShard.setProvider( node.brokerCtx, proc( @@ -481,8 +540,60 @@ proc startProvidersAndListeners(node: WakuNode) = ).isOkOr: error "Can't set provider for RequestRelayShard", error = error -proc stopProvidersAndListeners(node: WakuNode) = + RequestShardTopicsHealth.setProvider( + node.brokerCtx, + proc(topics: seq[PubsubTopic]): Result[RequestShardTopicsHealth, string] = + var response: RequestShardTopicsHealth + + for shard in topics: + var healthStatus = TopicHealth.UNHEALTHY + + if not node.wakuRelay.isNil: + healthStatus = + node.wakuRelay.topicsHealth.getOrDefault(shard, TopicHealth.NOT_SUBSCRIBED) + + if healthStatus == TopicHealth.NOT_SUBSCRIBED: + healthStatus = node.calculateEdgeTopicHealth(shard) + + response.topicHealth.add((shard, healthStatus)) + + return ok(response), + ).isOkOr: + error "Can't set provider for RequestShardTopicsHealth", error = error + + RequestContentTopicsHealth.setProvider( + node.brokerCtx, + proc(topics: seq[ContentTopic]): Result[RequestContentTopicsHealth, string] = + var response: RequestContentTopicsHealth + + for contentTopic in topics: + var topicHealth = TopicHealth.NOT_SUBSCRIBED + + let shardResult = node.deduceRelayShard(contentTopic, none[PubsubTopic]()) + + if shardResult.isOk(): + let shardObj = shardResult.get() + let pubsubTopic = $shardObj + if not isNil(node.wakuRelay): + topicHealth = node.wakuRelay.topicsHealth.getOrDefault( + pubsubTopic, TopicHealth.NOT_SUBSCRIBED + ) + + if topicHealth == TopicHealth.NOT_SUBSCRIBED and + pubsubTopic in node.edgeTopicsHealth: + topicHealth = node.calculateEdgeTopicHealth(pubsubTopic) + + response.contentTopicHealth.add((topic: contentTopic, health: topicHealth)) + + return ok(response), + ).isOkOr: + error "Can't set provider for RequestContentTopicsHealth", error = error + +proc stopProvidersAndListeners*(node: WakuNode) = + EventWakuPeer.dropListener(node.brokerCtx, node.peerEventListener) RequestRelayShard.clearProvider(node.brokerCtx) + RequestContentTopicsHealth.clearProvider(node.brokerCtx) + RequestShardTopicsHealth.clearProvider(node.brokerCtx) proc start*(node: WakuNode) {.async.} = ## Starts a created Waku Node and @@ -532,6 +643,9 @@ proc start*(node: WakuNode) {.async.} = ## The switch will update addresses after start using the addressMapper await node.switch.start() + node.edgeHealthEvent = newAsyncEvent() + node.edgeHealthLoop = loopEdgeHealth(node) + node.startProvidersAndListeners() node.started = true @@ -549,6 +663,10 @@ proc stop*(node: WakuNode) {.async.} = node.stopProvidersAndListeners() + if not node.edgeHealthLoop.isNil: + await node.edgeHealthLoop.cancelAndWait() + node.edgeHealthLoop = nil + await node.switch.stop() node.peerManager.stop() diff --git a/waku/requests/health_request.nim b/waku/requests/health_request.nim deleted file mode 100644 index 9f98eba67..000000000 --- a/waku/requests/health_request.nim +++ /dev/null @@ -1,21 +0,0 @@ -import waku/common/broker/[request_broker, multi_request_broker] - -import waku/api/types -import waku/node/health_monitor/[protocol_health, topic_health] -import waku/waku_core/topics - -export protocol_health, topic_health - -RequestBroker(sync): - type RequestNodeHealth* = object - healthStatus*: NodeHealth - -RequestBroker(sync): - type RequestRelayTopicsHealth* = object - topicHealth*: seq[tuple[topic: PubsubTopic, health: TopicHealth]] - - proc signature(topics: seq[PubsubTopic]): Result[RequestRelayTopicsHealth, string] - -MultiRequestBroker: - type RequestProtocolHealth* = object - healthStatus*: ProtocolHealth diff --git a/waku/requests/health_requests.nim b/waku/requests/health_requests.nim new file mode 100644 index 000000000..3554922b3 --- /dev/null +++ b/waku/requests/health_requests.nim @@ -0,0 +1,39 @@ +import waku/common/broker/request_broker + +import waku/api/types +import waku/node/health_monitor/[protocol_health, topic_health, health_report] +import waku/waku_core/topics +import waku/common/waku_protocol + +export protocol_health, topic_health + +# Get the overall node connectivity status +RequestBroker(sync): + type RequestConnectionStatus* = object + connectionStatus*: ConnectionStatus + +# Get the health status of a set of content topics +RequestBroker(sync): + type RequestContentTopicsHealth* = object + contentTopicHealth*: seq[tuple[topic: ContentTopic, health: TopicHealth]] + + proc signature(topics: seq[ContentTopic]): Result[RequestContentTopicsHealth, string] + +# Get a consolidated node health report +RequestBroker: + type RequestHealthReport* = object + healthReport*: HealthReport + +# Get the health status of a set of shards (pubsub topics) +RequestBroker(sync): + type RequestShardTopicsHealth* = object + topicHealth*: seq[tuple[topic: PubsubTopic, health: TopicHealth]] + + proc signature(topics: seq[PubsubTopic]): Result[RequestShardTopicsHealth, string] + +# Get the health status of a mounted protocol +RequestBroker: + type RequestProtocolHealth* = object + healthStatus*: ProtocolHealth + + proc signature(protocol: WakuProtocol): Future[Result[RequestProtocolHealth, string]] diff --git a/waku/requests/requests.nim b/waku/requests/requests.nim index 03e10f882..9225c0f3e 100644 --- a/waku/requests/requests.nim +++ b/waku/requests/requests.nim @@ -1,3 +1,3 @@ -import ./[health_request, rln_requests, node_requests] +import ./[health_requests, rln_requests, node_requests] -export health_request, rln_requests, node_requests +export health_requests, rln_requests, node_requests diff --git a/waku/rest_api/endpoint/health/types.nim b/waku/rest_api/endpoint/health/types.nim index 57f8b284c..88fa736a8 100644 --- a/waku/rest_api/endpoint/health/types.nim +++ b/waku/rest_api/endpoint/health/types.nim @@ -2,7 +2,8 @@ import results import chronicles, json_serialization, json_serialization/std/options -import ../../../waku_node, ../serdes +import ../serdes +import waku/[waku_node, api/types] #### Serialization and deserialization @@ -44,6 +45,7 @@ proc writeValue*( ) {.raises: [IOError].} = writer.beginRecord() writer.writeField("nodeHealth", $value.nodeHealth) + writer.writeField("connectionStatus", $value.connectionStatus) writer.writeField("protocolsHealth", value.protocolsHealth) writer.endRecord() @@ -52,6 +54,7 @@ proc readValue*( ) {.raises: [SerializationError, IOError].} = var nodeHealth: Option[HealthStatus] + connectionStatus: Option[ConnectionStatus] protocolsHealth: Option[seq[ProtocolHealth]] for fieldName in readObjectFields(reader): @@ -66,6 +69,16 @@ proc readValue*( reader.raiseUnexpectedValue("Invalid `health` value: " & $error) nodeHealth = some(health) + of "connectionStatus": + if connectionStatus.isSome(): + reader.raiseUnexpectedField( + "Multiple `connectionStatus` fields found", "HealthReport" + ) + + let state = ConnectionStatus.init(reader.readValue(string)).valueOr: + reader.raiseUnexpectedValue("Invalid `connectionStatus` value: " & $error) + + connectionStatus = some(state) of "protocolsHealth": if protocolsHealth.isSome(): reader.raiseUnexpectedField( @@ -79,5 +92,8 @@ proc readValue*( if nodeHealth.isNone(): reader.raiseUnexpectedValue("Field `nodeHealth` is missing") - value = - HealthReport(nodeHealth: nodeHealth.get, protocolsHealth: protocolsHealth.get(@[])) + value = HealthReport( + nodeHealth: nodeHealth.get, + connectionStatus: connectionStatus.get, + protocolsHealth: protocolsHealth.get(@[]), + ) diff --git a/waku/waku_relay/protocol.nim b/waku/waku_relay/protocol.nim index 3f343269a..17470af29 100644 --- a/waku/waku_relay/protocol.nim +++ b/waku/waku_relay/protocol.nim @@ -5,7 +5,7 @@ {.push raises: [].} import - std/[strformat, strutils], + std/[strformat, strutils, sets], stew/byteutils, results, sequtils, @@ -21,11 +21,13 @@ import import waku/waku_core, waku/node/health_monitor/topic_health, - waku/requests/health_request, + waku/requests/health_requests, + waku/events/health_events, ./message_id, - waku/common/broker/broker_context + waku/common/broker/broker_context, + waku/events/peer_events -from ../waku_core/codecs import WakuRelayCodec +from waku/waku_core/codecs import WakuRelayCodec export WakuRelayCodec type ShardMetrics = object @@ -154,6 +156,8 @@ type pubsubTopic: PubsubTopic, message: WakuMessage ): Future[ValidationResult] {.gcsafe, raises: [Defect].} WakuRelay* = ref object of GossipSub + brokerCtx: BrokerContext + peerEventListener: EventWakuPeerListener # seq of tuples: the first entry in the tuple contains the validators are called for every topic # the second entry contains the error messages to be returned when the validator fails wakuValidators: seq[tuple[handler: WakuValidatorHandler, errorMessage: string]] @@ -165,6 +169,11 @@ type topicsHealth*: Table[string, TopicHealth] onTopicHealthChange*: TopicHealthChangeHandler topicHealthLoopHandle*: Future[void] + topicHealthUpdateEvent: AsyncEvent + topicHealthDirty: HashSet[string] + # list of topics that need their health updated in the update event + topicHealthCheckAll: bool + # true if all topics need to have their health status refreshed in the update event msgMetricsPerShard*: Table[string, ShardMetrics] # predefinition for more detailed results from publishing new message @@ -287,6 +296,21 @@ proc initRelayObservers(w: WakuRelay) = ) proc onRecv(peer: PubSubPeer, msgs: var RPCMsg) = + if msgs.control.isSome(): + let ctrl = msgs.control.get() + var topicsChanged = false + + for graft in ctrl.graft: + w.topicHealthDirty.incl(graft.topicID) + topicsChanged = true + + for prune in ctrl.prune: + w.topicHealthDirty.incl(prune.topicID) + topicsChanged = true + + if topicsChanged: + w.topicHealthUpdateEvent.fire() + for msg in msgs.messages: let (msg_id_short, topic, wakuMessage, msgSize) = decodeRpcMessageInfo(peer, msg).valueOr: continue @@ -325,18 +349,6 @@ proc initRelayObservers(w: WakuRelay) = w.addObserver(administrativeObserver) -proc initRequestProviders(w: WakuRelay) = - RequestRelayTopicsHealth.setProvider( - globalBrokerContext(), - proc(topics: seq[PubsubTopic]): Result[RequestRelayTopicsHealth, string] = - var collectedRes: RequestRelayTopicsHealth - for topic in topics: - let health = w.topicsHealth.getOrDefault(topic, TopicHealth.NOT_SUBSCRIBED) - collectedRes.topicHealth.add((topic, health)) - return ok(collectedRes), - ).isOkOr: - error "Cannot set Relay Topics Health request provider", error = error - proc new*( T: type WakuRelay, switch: Switch, maxMessageSize = int(DefaultMaxWakuMessageSize) ): WakuRelayResult[T] = @@ -354,12 +366,25 @@ proc new*( maxMessageSize = maxMessageSize, parameters = GossipsubParameters, ) + w.brokerCtx = globalBrokerContext() procCall GossipSub(w).initPubSub() w.topicsHealth = initTable[string, TopicHealth]() + w.topicHealthUpdateEvent = newAsyncEvent() + w.topicHealthDirty = initHashSet[string]() + w.topicHealthCheckAll = false w.initProtocolHandler() w.initRelayObservers() - w.initRequestProviders() + + w.peerEventListener = EventWakuPeer.listen( + w.brokerCtx, + proc(evt: EventWakuPeer): Future[void] {.async: (raises: []), gcsafe.} = + if evt.kind == WakuPeerEventKind.EventDisconnected: + w.topicHealthCheckAll = true + w.topicHealthUpdateEvent.fire() + , + ).valueOr: + return err("Failed to subscribe to peer events: " & error) except InitializationError: return err("initialization error: " & getCurrentExceptionMsg()) @@ -437,38 +462,58 @@ proc calculateTopicHealth(wakuRelay: WakuRelay, topic: string): TopicHealth = return TopicHealth.MINIMALLY_HEALTHY return TopicHealth.SUFFICIENTLY_HEALTHY -proc updateTopicsHealth(wakuRelay: WakuRelay) {.async.} = - var futs = newSeq[Future[void]]() - for topic in toSeq(wakuRelay.topics.keys): - ## loop over all the topics I'm subscribed to - let - oldHealth = wakuRelay.topicsHealth.getOrDefault(topic) - currentHealth = wakuRelay.calculateTopicHealth(topic) +proc isSubscribed*(w: WakuRelay, topic: PubsubTopic): bool = + GossipSub(w).topics.hasKey(topic) - if oldHealth == currentHealth: - continue +proc subscribedTopics*(w: WakuRelay): seq[PubsubTopic] = + return toSeq(GossipSub(w).topics.keys()) - wakuRelay.topicsHealth[topic] = currentHealth - if not wakuRelay.onTopicHealthChange.isNil(): - let fut = wakuRelay.onTopicHealthChange(topic, currentHealth) - if not fut.completed(): # Fast path for successful sync handlers - futs.add(fut) +proc topicsHealthLoop(w: WakuRelay) {.async.} = + while true: + await w.topicHealthUpdateEvent.wait() + w.topicHealthUpdateEvent.clear() + + var topicsToCheck: seq[string] + + if w.topicHealthCheckAll: + topicsToCheck = toSeq(w.topics.keys) + else: + topicsToCheck = toSeq(w.topicHealthDirty) + + w.topicHealthCheckAll = false + w.topicHealthDirty.clear() + + var futs = newSeq[Future[void]]() + + for topic in topicsToCheck: + # guard against topic being unsubscribed since fire() + if not w.isSubscribed(topic): + continue + + let + oldHealth = w.topicsHealth.getOrDefault(topic, TopicHealth.UNHEALTHY) + currentHealth = w.calculateTopicHealth(topic) + + if oldHealth == currentHealth: + continue + + w.topicsHealth[topic] = currentHealth + + EventShardTopicHealthChange.emit(w.brokerCtx, topic, currentHealth) + + if not w.onTopicHealthChange.isNil(): + futs.add(w.onTopicHealthChange(topic, currentHealth)) if futs.len() > 0: - # slow path - we have to wait for the handlers to complete try: - futs = await allFinished(futs) + discard await allFinished(futs) except CancelledError: - # check for errors in futures - for fut in futs: - if fut.failed: - let err = fut.readError() - warn "Error in health change handler", description = err.msg + break + except CatchableError as e: + warn "Error in topic health callback", error = e.msg -proc topicsHealthLoop(wakuRelay: WakuRelay) {.async.} = - while true: - await wakuRelay.updateTopicsHealth() - await sleepAsync(10.seconds) + # safety cooldown to protect from edge cases + await sleepAsync(100.milliseconds) method start*(w: WakuRelay) {.async, base.} = info "start" @@ -478,15 +523,13 @@ method start*(w: WakuRelay) {.async, base.} = method stop*(w: WakuRelay) {.async, base.} = info "stop" await procCall GossipSub(w).stop() + + if w.peerEventListener.id != 0: + EventWakuPeer.dropListener(w.brokerCtx, w.peerEventListener) + if not w.topicHealthLoopHandle.isNil(): await w.topicHealthLoopHandle.cancelAndWait() -proc isSubscribed*(w: WakuRelay, topic: PubsubTopic): bool = - GossipSub(w).topics.hasKey(topic) - -proc subscribedTopics*(w: WakuRelay): seq[PubsubTopic] = - return toSeq(GossipSub(w).topics.keys()) - proc generateOrderedValidator(w: WakuRelay): ValidatorHandler {.gcsafe.} = # rejects messages that are not WakuMessage let wrappedValidator = proc( @@ -584,7 +627,8 @@ proc subscribe*(w: WakuRelay, pubsubTopic: PubsubTopic, handler: WakuRelayHandle procCall GossipSub(w).subscribe(pubsubTopic, topicHandler) w.topicHandlers[pubsubTopic] = topicHandler - asyncSpawn w.updateTopicsHealth() + w.topicHealthDirty.incl(pubsubTopic) + w.topicHealthUpdateEvent.fire() proc unsubscribeAll*(w: WakuRelay, pubsubTopic: PubsubTopic) = ## Unsubscribe all handlers on this pubsub topic @@ -594,6 +638,8 @@ proc unsubscribeAll*(w: WakuRelay, pubsubTopic: PubsubTopic) = procCall GossipSub(w).unsubscribeAll(pubsubTopic) w.topicValidator.del(pubsubTopic) w.topicHandlers.del(pubsubTopic) + w.topicsHealth.del(pubsubTopic) + w.topicHealthDirty.excl(pubsubTopic) proc unsubscribe*(w: WakuRelay, pubsubTopic: PubsubTopic) = if not w.topicValidator.hasKey(pubsubTopic): @@ -619,6 +665,8 @@ proc unsubscribe*(w: WakuRelay, pubsubTopic: PubsubTopic) = w.topicValidator.del(pubsubTopic) w.topicHandlers.del(pubsubTopic) + w.topicsHealth.del(pubsubTopic) + w.topicHealthDirty.excl(pubsubTopic) proc publish*( w: WakuRelay, pubsubTopic: PubsubTopic, wakuMessage: WakuMessage From 84f791100fcc4367ca17a9d999e9aa943359dd51 Mon Sep 17 00:00:00 2001 From: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:23:21 +0100 Subject: [PATCH 6/8] fix: peer selection by shard and rendezvous/metadata sharding initialization (#3718) * Fix peer selection for cases where ENR is not yet advertiesed but metadata exchange already adjusted supported shards. Fix initialization rendezvous protocol with configured and autoshards to let connect to relay nodes without having a valid subscribed shard already. This solves issue for autoshard nodes to connect ahead of subscribing. * Extend peer selection, rendezvous and metadata tests * Fix rendezvous test, fix metadata test failing due wrong setup, added it into all_tests --- tests/all_tests_waku.nim | 1 + tests/test_peer_manager.nim | 230 ++++++++++++++++++++++++ tests/test_waku_metadata.nim | 85 +++++++-- tests/test_waku_rendezvous.nim | 63 +++++++ waku/factory/node_factory.nim | 2 +- waku/node/peer_manager/peer_manager.nim | 14 +- waku/node/waku_node.nim | 28 ++- 7 files changed, 401 insertions(+), 22 deletions(-) diff --git a/tests/all_tests_waku.nim b/tests/all_tests_waku.nim index 3d22cd9c2..4d4225f9f 100644 --- a/tests/all_tests_waku.nim +++ b/tests/all_tests_waku.nim @@ -89,6 +89,7 @@ import ./test_waku_netconfig, ./test_waku_switch, ./test_waku_rendezvous, + ./test_waku_metadata, ./waku_discv5/test_waku_discv5 # Waku Keystore test suite diff --git a/tests/test_peer_manager.nim b/tests/test_peer_manager.nim index 97df39582..c96f21b6e 100644 --- a/tests/test_peer_manager.nim +++ b/tests/test_peer_manager.nim @@ -1207,3 +1207,233 @@ procSuite "Peer Manager": r = node1.peerManager.selectPeer(WakuPeerExchangeCodec) assert r.isSome(), "could not retrieve peer mounting WakuPeerExchangeCodec" + + asyncTest "selectPeer() filters peers by shard using ENR": + ## Given: A peer manager with 3 peers having different shards in their ENRs + let + clusterId = 0.uint16 + shardId0 = 0.uint16 + shardId1 = 1.uint16 + + # Create 3 nodes with different shards + let nodes = + @[ + newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[shardId0], + ), + newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[shardId1], + ), + newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[shardId0], + ), + ] + + await allFutures(nodes.mapIt(it.start())) + for node in nodes: + discard await node.mountRelay() + + # Get peer infos with ENRs + let peerInfos = collect: + for node in nodes: + var peerInfo = node.switch.peerInfo.toRemotePeerInfo() + peerInfo.enr = some(node.enr) + peerInfo + + # Add all peers to node 0's peer manager and peerstore + for i in 1 .. 2: + nodes[0].peerManager.addPeer(peerInfos[i]) + nodes[0].peerManager.switch.peerStore[AddressBook][peerInfos[i].peerId] = + peerInfos[i].addrs + nodes[0].peerManager.switch.peerStore[ProtoBook][peerInfos[i].peerId] = + @[WakuRelayCodec] + + ## When: We select a peer for shard 0 + let shard0Topic = some(PubsubTopic("/waku/2/rs/0/0")) + let selectedPeer0 = nodes[0].peerManager.selectPeer(WakuRelayCodec, shard0Topic) + + ## Then: Only peers supporting shard 0 are considered (nodes 2, not node 1) + check: + selectedPeer0.isSome() + selectedPeer0.get().peerId != peerInfos[1].peerId # node1 has shard 1 + selectedPeer0.get().peerId == peerInfos[2].peerId # node2 has shard 0 + + ## When: We select a peer for shard 1 + let shard1Topic = some(PubsubTopic("/waku/2/rs/0/1")) + let selectedPeer1 = nodes[0].peerManager.selectPeer(WakuRelayCodec, shard1Topic) + + ## Then: Only peer with shard 1 is selected + check: + selectedPeer1.isSome() + selectedPeer1.get().peerId == peerInfos[1].peerId # node1 has shard 1 + + await allFutures(nodes.mapIt(it.stop())) + + asyncTest "selectPeer() filters peers by shard using shards field": + ## Given: A peer manager with peers having shards in RemotePeerInfo (no ENR) + let + clusterId = 0.uint16 + shardId0 = 0.uint16 + shardId1 = 1.uint16 + + # Create peer manager + let pm = PeerManager.new( + switch = SwitchBuilder.new().withRng(rng()).withMplex().withNoise().build(), + storage = nil, + ) + + # Create peer infos with shards field populated (simulating metadata exchange) + let basePeerId = "16Uiu2HAm7QGEZKujdSbbo1aaQyfDPQ6Bw3ybQnj6fruH5Dxwd7D" + let peers = toSeq(1 .. 3) + .mapIt(parsePeerInfo("/ip4/0.0.0.0/tcp/30300/p2p/" & basePeerId & $it)) + .filterIt(it.isOk()) + .mapIt(it.value) + require: + peers.len == 3 + + # Manually populate the shards field (ENR is not available) + var peerInfos: seq[RemotePeerInfo] = @[] + for i, peer in peers: + var peerInfo = RemotePeerInfo.init(peer.peerId, peer.addrs) + # Peer 0 and 2 have shard 0, peer 1 has shard 1 + peerInfo.shards = + if i == 1: + @[shardId1] + else: + @[shardId0] + # Note: ENR is intentionally left as none + peerInfos.add(peerInfo) + + # Add peers to peerstore + for peerInfo in peerInfos: + pm.switch.peerStore[AddressBook][peerInfo.peerId] = peerInfo.addrs + pm.switch.peerStore[ProtoBook][peerInfo.peerId] = @[WakuRelayCodec] + # simulate metadata exchange by setting shards field in peerstore + pm.switch.peerStore.setShardInfo(peerInfo.peerId, peerInfo.shards) + + ## When: We select a peer for shard 0 + let shard0Topic = some(PubsubTopic("/waku/2/rs/0/0")) + let selectedPeer0 = pm.selectPeer(WakuRelayCodec, shard0Topic) + + ## Then: Peers with shard 0 in shards field are selected + check: + selectedPeer0.isSome() + selectedPeer0.get().peerId in [peerInfos[0].peerId, peerInfos[2].peerId] + + ## When: We select a peer for shard 1 + let shard1Topic = some(PubsubTopic("/waku/2/rs/0/1")) + let selectedPeer1 = pm.selectPeer(WakuRelayCodec, shard1Topic) + + ## Then: Peer with shard 1 in shards field is selected + check: + selectedPeer1.isSome() + selectedPeer1.get().peerId == peerInfos[1].peerId + + asyncTest "selectPeer() handles invalid pubsub topic gracefully": + ## Given: A peer manager with valid peers + let node = newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = 0, + subscribeShards = @[0'u16], + ) + await node.start() + + # Add a peer + let peer = + newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + await peer.start() + discard await peer.mountRelay() + + var peerInfo = peer.switch.peerInfo.toRemotePeerInfo() + peerInfo.enr = some(peer.enr) + node.peerManager.addPeer(peerInfo) + node.peerManager.switch.peerStore[ProtoBook][peerInfo.peerId] = @[WakuRelayCodec] + + ## When: selectPeer is called with malformed pubsub topic + let invalidTopics = + @[ + some(PubsubTopic("invalid-topic")), + some(PubsubTopic("/waku/2/invalid")), + some(PubsubTopic("/waku/2/rs/abc/0")), # non-numeric cluster + some(PubsubTopic("")), # empty topic + ] + + ## Then: Returns none(RemotePeerInfo) without crashing + for invalidTopic in invalidTopics: + let result = node.peerManager.selectPeer(WakuRelayCodec, invalidTopic) + check: + result.isNone() + + await allFutures(node.stop(), peer.stop()) + + asyncTest "selectPeer() prioritizes ENR over shards field": + ## Given: A peer with both ENR and shards field populated + let + clusterId = 0.uint16 + shardId0 = 0.uint16 + shardId1 = 1.uint16 + + let node = newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[shardId0], + ) + await node.start() + discard await node.mountRelay() + + # Create peer with ENR containing shard 0 + let peer = newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[shardId0], + ) + await peer.start() + discard await peer.mountRelay() + + # Create peer info with ENR (shard 0) but set shards field to shard 1 + var peerInfo = peer.switch.peerInfo.toRemotePeerInfo() + peerInfo.enr = some(peer.enr) # ENR has shard 0 + peerInfo.shards = @[shardId1] # shards field has shard 1 + + node.peerManager.addPeer(peerInfo) + node.peerManager.switch.peerStore[ProtoBook][peerInfo.peerId] = @[WakuRelayCodec] + # simulate metadata exchange by setting shards field in peerstore + node.peerManager.switch.peerStore.setShardInfo(peerInfo.peerId, peerInfo.shards) + + ## When: We select for shard 0 + let shard0Topic = some(PubsubTopic("/waku/2/rs/0/0")) + let selectedPeer = node.peerManager.selectPeer(WakuRelayCodec, shard0Topic) + + ## Then: Peer is selected because ENR (shard 0) takes precedence + check: + selectedPeer.isSome() + selectedPeer.get().peerId == peerInfo.peerId + + ## When: We select for shard 1 + let shard1Topic = some(PubsubTopic("/waku/2/rs/0/1")) + let selectedPeer1 = node.peerManager.selectPeer(WakuRelayCodec, shard1Topic) + + ## Then: Peer is still selected because shards field is checked as fallback + check: + selectedPeer1.isSome() + selectedPeer1.get().peerId == peerInfo.peerId + + await allFutures(node.stop(), peer.stop()) diff --git a/tests/test_waku_metadata.nim b/tests/test_waku_metadata.nim index b30fd1712..cfceb89b5 100644 --- a/tests/test_waku_metadata.nim +++ b/tests/test_waku_metadata.nim @@ -13,14 +13,15 @@ import eth/keys, eth/p2p/discoveryv5/enr import - waku/ - [ - waku_node, - waku_core/topics, - node/peer_manager, - discovery/waku_discv5, - waku_metadata, - ], + waku/[ + waku_node, + waku_core/topics, + waku_core, + node/peer_manager, + discovery/waku_discv5, + waku_metadata, + waku_relay/protocol, + ], ./testlib/wakucore, ./testlib/wakunode @@ -41,26 +42,86 @@ procSuite "Waku Metadata Protocol": clusterId = clusterId, ) + # Mount metadata protocol on both nodes before starting + discard node1.mountMetadata(clusterId, @[]) + discard node2.mountMetadata(clusterId, @[]) + + # Mount relay so metadata can track subscriptions + discard await node1.mountRelay() + discard await node2.mountRelay() + # Start nodes await allFutures([node1.start(), node2.start()]) - node1.topicSubscriptionQueue.emit((kind: PubsubSub, topic: "/waku/2/rs/10/7")) - node1.topicSubscriptionQueue.emit((kind: PubsubSub, topic: "/waku/2/rs/10/6")) + # Subscribe to topics on node1 - relay will track these and metadata will report them + let noOpHandler: WakuRelayHandler = proc( + pubsubTopic: PubsubTopic, message: WakuMessage + ): Future[void] {.async.} = + discard + + node1.wakuRelay.subscribe("/waku/2/rs/10/7", noOpHandler) + node1.wakuRelay.subscribe("/waku/2/rs/10/6", noOpHandler) # Create connection let connOpt = await node2.peerManager.dialPeer( node1.switch.peerInfo.toRemotePeerInfo(), WakuMetadataCodec ) require: - connOpt.isSome + connOpt.isSome() # Request metadata let response1 = await node2.wakuMetadata.request(connOpt.get()) # Check the response or dont even continue require: - response1.isOk + response1.isOk() check: response1.get().clusterId.get() == clusterId response1.get().shards == @[uint32(6), uint32(7)] + + await allFutures([node1.stop(), node2.stop()]) + + asyncTest "Metadata reports configured shards before relay subscription": + ## Given: Node with configured shards but no relay subscriptions yet + let + clusterId = 10.uint16 + configuredShards = @[uint16(0), uint16(1)] + + let node1 = newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = configuredShards, + ) + let node2 = newTestWakuNode( + generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0), clusterId = clusterId + ) + + # Mount metadata with configured shards on node1 + discard node1.mountMetadata(clusterId, configuredShards) + # Mount metadata on node2 so it can make requests + discard node2.mountMetadata(clusterId, @[]) + + # Start nodes (relay is NOT mounted yet on node1) + await allFutures([node1.start(), node2.start()]) + + ## When: Node2 requests metadata from Node1 before relay is active + let connOpt = await node2.peerManager.dialPeer( + node1.switch.peerInfo.toRemotePeerInfo(), WakuMetadataCodec + ) + require: + connOpt.isSome + + let response = await node2.wakuMetadata.request(connOpt.get()) + + ## Then: Response contains configured shards even without relay subscriptions + require: + response.isOk() + + check: + response.get().clusterId.get() == clusterId + response.get().shards == @[uint32(0), uint32(1)] + + await allFutures([node1.stop(), node2.stop()]) diff --git a/tests/test_waku_rendezvous.nim b/tests/test_waku_rendezvous.nim index d3dd6f920..07113ca4a 100644 --- a/tests/test_waku_rendezvous.nim +++ b/tests/test_waku_rendezvous.nim @@ -10,6 +10,7 @@ import import waku/waku_core/peers, waku/waku_core/codecs, + waku/waku_core, waku/node/waku_node, waku/node/peer_manager/peer_manager, waku/waku_rendezvous/protocol, @@ -81,3 +82,65 @@ procSuite "Waku Rendezvous": records.len == 1 records[0].peerId == peerInfo1.peerId #records[0].mixPubKey == $node1.wakuMix.pubKey + + asyncTest "Rendezvous advertises configured shards before relay is active": + ## Given: A node with configured shards but no relay subscriptions yet + let + clusterId = 10.uint16 + configuredShards = @[RelayShard(clusterId: clusterId, shardId: 0)] + + let node = newTestWakuNode( + generateSecp256k1Key(), + parseIpAddress("0.0.0.0"), + Port(0), + clusterId = clusterId, + subscribeShards = @[0'u16], + ) + + ## When: Node mounts rendezvous with configured shards (before relay) + await node.mountRendezvous(clusterId, configuredShards) + await node.start() + + ## Then: The rendezvous protocol should be mounted successfully + check: + node.wakuRendezvous != nil + + # Verify that the protocol is running without errors + # (shards are used internally by the getShardsGetter closure) + let namespace = computeMixNamespace(clusterId) + check: + namespace.len > 0 + + await node.stop() + + asyncTest "Rendezvous uses configured shards when relay not mounted": + ## Given: A light client node with no relay protocol + let + clusterId = 10.uint16 + configuredShards = + @[ + RelayShard(clusterId: clusterId, shardId: 0), + RelayShard(clusterId: clusterId, shardId: 1), + ] + + let lightClient = newTestWakuNode( + generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0), clusterId = clusterId + ) + + ## When: Node mounts rendezvous with configured shards (no relay mounted) + await lightClient.mountRendezvous(clusterId, configuredShards) + await lightClient.start() + + ## Then: Rendezvous should be mounted successfully without relay + check: + lightClient.wakuRendezvous != nil + lightClient.wakuRelay == nil # Verify relay is not mounted + + # Verify the protocol is working (doesn't fail immediately) + # advertiseAll requires peers,so we just check the protocol is initialized + await sleepAsync(100.milliseconds) + + check: + lightClient.wakuRendezvous != nil + + await lightClient.stop() diff --git a/waku/factory/node_factory.nim b/waku/factory/node_factory.nim index 2cdfdb0d2..dc383e89d 100644 --- a/waku/factory/node_factory.nim +++ b/waku/factory/node_factory.nim @@ -337,7 +337,7 @@ proc setupProtocols( node.wakuRelay.addSignedShardsValidator(subscribedProtectedShards, conf.clusterId) if conf.rendezvous: - await node.mountRendezvous(conf.clusterId) + await node.mountRendezvous(conf.clusterId, shards) await node.mountRendezvousClient(conf.clusterId) # Keepalive mounted on all nodes diff --git a/waku/node/peer_manager/peer_manager.nim b/waku/node/peer_manager/peer_manager.nim index 834fb19cf..0c435468f 100644 --- a/waku/node/peer_manager/peer_manager.nim +++ b/waku/node/peer_manager/peer_manager.nim @@ -227,7 +227,19 @@ proc selectPeer*( protocol = proto, peers, address = cast[uint](pm.switch.peerStore) if shard.isSome(): - peers.keepItIf((it.enr.isSome() and it.enr.get().containsShard(shard.get()))) + # Parse the shard from the pubsub topic to get cluster and shard ID + let shardInfo = RelayShard.parse(shard.get()).valueOr: + trace "Failed to parse shard from pubsub topic", topic = shard.get() + return none(RemotePeerInfo) + + # Filter peers that support the requested shard + # Check both ENR (if present) and the shards field on RemotePeerInfo + peers.keepItIf( + # Check ENR if available + (it.enr.isSome() and it.enr.get().containsShard(shard.get())) or + # Otherwise check the shards field directly + (it.shards.len > 0 and it.shards.contains(shardInfo.shardId)) + ) shuffle(peers) diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index cb3d81c7c..53ce0349a 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -167,20 +167,28 @@ proc deduceRelayShard( return err("Invalid topic:" & pubsubTopic & " " & $error) return ok(shard) -proc getShardsGetter(node: WakuNode): GetShards = +proc getShardsGetter(node: WakuNode, configuredShards: seq[uint16]): GetShards = return proc(): seq[uint16] {.closure, gcsafe, raises: [].} = # fetch pubsubTopics subscribed to relay and convert them to shards if node.wakuRelay.isNil(): - return @[] + # If relay is not mounted, return configured shards + return configuredShards + let subscribedTopics = node.wakuRelay.subscribedTopics() + + # If relay hasn't subscribed to any topics yet, return configured shards + if subscribedTopics.len == 0: + return configuredShards + let relayShards = topicsToRelayShards(subscribedTopics).valueOr: error "could not convert relay topics to shards", error = $error, topics = subscribedTopics - return @[] + # Fall back to configured shards on error + return configuredShards if relayShards.isSome(): let shards = relayShards.get().shardIds return shards - return @[] + return configuredShards proc getCapabilitiesGetter(node: WakuNode): GetCapabilities = return proc(): seq[Capabilities] {.closure, gcsafe, raises: [].} = @@ -227,7 +235,7 @@ proc new*( rateLimitSettings: rateLimitSettings, ) - peerManager.setShardGetter(node.getShardsGetter()) + peerManager.setShardGetter(node.getShardsGetter(@[])) return node @@ -272,7 +280,7 @@ proc mountMetadata*( if not node.wakuMetadata.isNil(): return err("Waku metadata already mounted, skipping") - let metadata = WakuMetadata.new(clusterId, node.getShardsGetter()) + let metadata = WakuMetadata.new(clusterId, node.getShardsGetter(shards)) node.wakuMetadata = metadata node.peerManager.wakuMetadata = metadata @@ -413,14 +421,18 @@ proc mountRendezvousClient*(node: WakuNode, clusterId: uint16) {.async: (raises: if node.started: await node.wakuRendezvousClient.start() -proc mountRendezvous*(node: WakuNode, clusterId: uint16) {.async: (raises: []).} = +proc mountRendezvous*( + node: WakuNode, clusterId: uint16, shards: seq[RelayShard] = @[] +) {.async: (raises: []).} = info "mounting rendezvous discovery protocol" + let configuredShards = shards.mapIt(it.shardId) + node.wakuRendezvous = WakuRendezVous.new( node.switch, node.peerManager, clusterId, - node.getShardsGetter(), + node.getShardsGetter(configuredShards), node.getCapabilitiesGetter(), node.getWakuPeerRecordGetter(), ).valueOr: From eb0c34c553c4a0ba7f1806d14083e6ef1e49dc92 Mon Sep 17 00:00:00 2001 From: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:55:31 +0100 Subject: [PATCH 7/8] Adjust docker file to bsd (#3720) * add libbsd-dev into Dockerfile * add libstdc++ in Dockerfile to avoid runtime error loading shared library libstdc++.so.6: No such file or directory (needed by /usr/bin/wakunode) --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 90fb0a9c9..5b16b9eee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG LOG_LEVEL=TRACE ARG HEAPTRACK_BUILD=0 # Get build tools and required header files -RUN apk add --no-cache bash git build-base openssl-dev linux-headers curl jq +RUN apk add --no-cache bash git build-base openssl-dev linux-headers curl jq libbsd-dev WORKDIR /app COPY . . @@ -46,7 +46,7 @@ LABEL version="unknown" EXPOSE 30303 60000 8545 # Referenced in the binary -RUN apk add --no-cache libgcc libpq-dev bind-tools +RUN apk add --no-cache libgcc libpq-dev bind-tools libstdc++ # Copy to separate location to accomodate different MAKE_TARGET values COPY --from=nim-build /app/build/$MAKE_TARGET /usr/local/bin/ From 8f29070dcfc7c621fdcd3941369fd2cdc81b69e7 Mon Sep 17 00:00:00 2001 From: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:49:35 +0100 Subject: [PATCH 8/8] fix avoid IndexDefect if DB error message is short (#3725) --- waku/common/databases/db_postgres/dbconn.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/waku/common/databases/db_postgres/dbconn.nim b/waku/common/databases/db_postgres/dbconn.nim index a6c237ae5..7ccf32099 100644 --- a/waku/common/databases/db_postgres/dbconn.nim +++ b/waku/common/databases/db_postgres/dbconn.nim @@ -48,8 +48,8 @@ proc check(db: DbConn): Result[void, string] = return err("exception in check: " & getCurrentExceptionMsg()) if message.len > 0: - let truncatedErr = message[0 .. 80] - ## libpq sometimes gives extremely long error messages + let truncatedErr = message[0 ..< min(80, message.len)] + error "postgres check issue. see truncated db error.", error = truncatedErr return err(truncatedErr) return ok()