Commit Graph
2450 Commits
Author SHA1 Message Date
Ivan FBandClaude Opus 5 c3a1b6b052 test(archive): pin the insert counter to what really got written
Each test writes once through a driver that refuses everything and once
through a working one: the first half keeps the counter tied to successful
writes -- it is what fails if the increment ever moves above the error check
and turns into an attempt counter -- and the second half is what fails if the
increment is dropped. The counters are process-global, so both compare against
a baseline read inside the test rather than an absolute value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:15:59 +02:00
Ivan FBandClaude Opus 5 2b5946e924 fix(archive): log a failed insert at a level the fleets keep
Both ingress paths logged a failed write at trace, which is compiled out at the
level the fleets run, so a node discarding every message it received said
nothing at all. Debug is enough to make it visible -- the delivery fleet runs
at DEBUG, see infra-logos ansible/group_vars/delivery.yml -- and it keeps a
broken database from turning every message into an error line.

Left untested on purpose: there is no capture of the log output in this repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:15:59 +02:00
Ivan FBandClaude Opus 5 b1fecb5051 feat(archive): count the messages the node actually writes
Nothing today tells a store node that writes nothing from a healthy one: the
per-shard gauge resets on restart and never sees the store-sync ingress, and
the stored-message count is a half-hourly total that retention keeps flat and
that silently goes stale when its query fails. Count every successful write on
both ingress paths, labelled by which one it came through, so that a node
going quiet is a rate() of zero rather than a signal nobody has.

Both series are published at zero when the archive is built, so a node that
has not written yet is reported as writing nothing instead of not reported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:34:48 +02:00
Igor Sirotin 69fbffa3a0 fix(nix): emit and install the generated C header (#4136) 2026-08-20 10:16:14 +02:00
Gabriel Cruz 1782941975 chore: bump nim-ffi to v0.3.1 (#4135) 2026-08-19 15:41:41 -03:00
Gabriel Cruz 27e0767e3b feat(ffi): pin nim-ffi 0.3.0 and stop the node on destroy (#4111) 2026-08-19 12:42:04 -03:00
Tanya S 195917898c Rework logging to be useful (#4110)
* Rework WARN and ERR logs during startup

* Clean up noise from start up logs

* Rework node hot path logs to only have crucial logs at INFO level

* Logs rework archive, filter, LP, peer ex, rdv, store

* Rework logs for waku_relay and rln

* rework logs for waku_node layer

* rework lgos for waku common and core

* Rework discovery and factory logs

* Rework logs for rest api endpoints

* Rework logs for messaging delivery service and channel lifecycle

* Rework logs in api directories

* Minor log cleanups

* Add info log for message recv by msg or reliable channels API

* Add metric to send_service, revert node/rln metrics to INFO lvl

* return WARN lvl to build conf preset

* revert log lvl to error for healthLoop exception

* Log error level for failures to serve API level requests

* Revert some debug logs to error or warn level
2026-08-19 13:55:15 +02:00
Darshan bc4a64f025 fix: partition messages_lookup to stop the index bloat (#4106) 2026-08-19 14:16:30 +05:30
Fabiana Cecin 517bef1591 fix(ci): daily build times out (#4126) 2026-08-18 16:42:15 -03:00
NagyZoltanPeter d3c0979dd5 chore(docker): rename compose folder and fix review follow-ups from #4057 (#4129)
Follow-up to the review of #4057.

- Rename apps/logos_delivery_node/compose -> apps/logos_delivery_node/docker
  (review request); the only path reference was in the folder's own README.
- run_certbot.sh: pass the configured EMAIL to certbot instead of the
  hardcoded admin@$DOMAIN, and forward EMAIL to the certbot container so
  setting it in .env takes effect. Empty still falls back to admin@$DOMAIN.
- set_storage_retention.sh: `grep -c` always prints a count, so the old
  sudo test was always true and sudo was used whenever ./postgresql existed.
  Test readability of the data directory directly instead.
- chkhealth.sh: drop the stray `GET` argument, which curl treated as an
  extra (failing) URL.
- docker-compose.yml: put certbot behind the `wss` profile so it no longer
  publishes host port 80 on deployments that do not use WebSocket-Secure,
  and drop the node's meaningless `depends_on: certbot` (run_node.sh polls
  for the certificate on disk anyway).
2026-08-18 18:03:04 +02:00
Egor Rachkovskii 2fb625f463 ci(e2e): point the S11/S25 store peers at the built image too (#4128)
The job set NODE_2 but not NODE_1, so the two docker store peers in
test_send_lightpush_and_edge.py (S11 and S25) silently fell back to the
DEFAULT_NWAKU literal, wakuorg/nwaku:v0.38.1, instead of the image built by
this run. Both tests are docker_required, so they run in the subset that
does receive node_image; they were the only NODE_1 readers in the suite and
the only ones missing out.

An empty node_image keeps working: get_env_var treats "" as unset, so the
non-docker subset and fork PRs still land on v0.38.1, exactly as NODE_2
already did.

DEFAULT_NWAKU is left as the hardcoded literal it is. Nothing reachable from
tests/wrappers_tests reads it except as the NODE_1/NODE_2 fallback -
src/steps/sharding.py and src/steps/rln.py are its only direct consumers and
neither is imported here - so making it env-driven would add a knob with no
effect. ADDITIONAL_NODES is likewise unused: its only readers,
setup_additional_publishing_nodes and setup_additional_store_nodes, have no
callers anywhere in tests-e2e.
2026-08-17 20:56:48 +01:00
NagyZoltanPeterandClaude Opus 4.8 0d433ea83f feat(compose): add logosdeliverynode docker-compose stack (#4057)
* feat(compose): add logosdeliverynode docker-compose stack

Add apps/logos_delivery_node/compose/, a docker-compose project that runs the
logosdeliverynode image (built from the repo Dockerfile) as a service node with
a Postgres store and a Prometheus + Grafana monitoring stack. Ported from
logos-messaging/logos-delivery-compose and adapted for logosdeliverynode:

- Network selection via --preset (default logos.dev) and --entry-layer (default
  kernel), both configurable through PRESET / ENTRY_LAYER env vars.
- Postgres-backed store; node startup gated on the postgres healthcheck to avoid
  a crash-loop on connection-refused.
- WebSocket-Secure via certbot, enabled only when DOMAIN is explicitly set (no
  reverse-DNS auto-guess, which could deadlock the node waiting for a cert).
- --mix=true passed explicitly (the preset alone sets the flag but does not mount
  the mix protocol).
- Grafana branded with the Logos mark; dashboard file named
  logos-delivery-monitoring.json.
- RLN, setup_wizard and RLN keystore tooling intentionally omitted for now.

Also exclude /nimbledeps and /build from the Docker build context: a populated
host nimbledeps/ leaks into the context and has its package submodules stripped
by the **/vendor/* rule, breaking the in-container `make build-deps`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): keep build/ in docker context; serialize container-image builds

Two fixes for the docker-build-ubuntu-22.04 job:

- .dockerignore: drop /build. docker/binaries/Dockerfile.bn.amd64 ADDs the
  freshly compiled binaries from ./build/, so ignoring it makes the docker
  build fail deterministically with '"/build/wakunode2": not found'.
  /nimbledeps stays ignored (that was the actual context-bloat culprit).

- container-image.yml: build wakunode2 and logosdeliverynode in sequential
  make invocations. Under a single `make -j` the two `nimble <task>`
  invocations re-resolve git deps concurrently and clobber each other in
  the shared ~/.nimble/pkgcache. Same fix as ci.yml's -j1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Review fixes: dashboard typo, missing target for docker build, env.example adjustment
2026-08-14 03:14:40 +02:00
Egor Rachkovskii 23b0d31e84 test(e2e): port the channel delivery tests and the isolated sender process (#4117)
Three open PRs in logos-delivery-interop-tests add 19 channel-API tests
(RC07-RC09 #203, RC10-RC12 #209, RC13 #212). Their wrapper API surface is
already covered here, but the pieces they import were not, so those tests could
not be written in core. This ports them.

subprocess_node.py, rc05/rc06 and the closed-channel test come from interop
master; the two wrapper_helpers additions are byte-identical to what #209/#212
add, so the RC07-RC13 ports apply cleanly.

Why a spawned subprocess: co-located nodes both default localStoragePath to
./data and share data/sds.db, so the receiver drops the sender's own channel
message as a duplicate. The interop rationale blaming a process-wide Persistency
singleton is false since #4109 made Persistency per-node; the shared storage path
is the cause, and chdir to a private temp dir is what isolates it. A per-node
localStoragePath would likely replace the spawn machinery, but RC07-RC13 build on
the ported form.

Why the re-subscribe in the closed-channel test: channel_close unsubscribes the
content topic and subscriptions are not refcounted (#4081, tracked in #4087), so
the receiver re-subscribes to keep message_received as the arrival witness. This
follows the resolution in the still-open interop PR #212.

Non-docker collection goes 48 -> 51; README updated. Suite: 48 passed, 2 skipped,
1 xfailed, verified over two back-to-back runs.
2026-08-12 19:03:09 +01:00
Fabiana Cecin e2a8c2b5d1 fix(ci): nightly pre-release and release-assets fixes (#4112) 2026-08-12 10:02:16 -03:00
Fabiana Cecin f4c5a703e0 fix(ffi): event listeners lifetime (#4118) 2026-08-12 09:59:41 -03:00
Igor SirotinandClaude Fable 5 56dda125ff ci: pull and publish the Logos Attic cache in nix builds (#4071)
Adopts logos-co/setup-nix-cache-action (as in logos-delivery-module#69);
flake.nix substituter moves from the retired status.im nix-cache to
cache.nix.logos.co/public.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 13:58:17 +01:00
Igor SirotinandClaude Opus 5 ef651a7ccc fix(networks): move logos.dev preset to cluster-id 3 (#4113)
* fix(networks): move logos.dev preset to cluster-id 3

The Logos Dev Network is now deployed on cluster 3. Update the
`logos.dev` preset accordingly, along with the CLI help text and the
library README preset table.

The deprecated "cluster-id implies preset" shim now triggers on
`--cluster-id=3` instead of `2`: cluster 2 is still the Logos Test
Network, so keeping the old mapping would silently move a node that
explicitly asked for cluster 2 onto cluster 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(conf): keep the legacy cluster-id preset shim on cluster 2

Remapping the deprecated `--cluster-id` -> preset shim onto cluster 3
hijacked every caller that uses 3 as a plain cluster id with no preset,
applying the whole logos.dev preset (p2p reliability, mix, discv5, dev
entry nodes) on top. tests/api/test_api_send.nim does exactly that, and
the extra reliability layer made Send emit `Sent` events the tests never
asked for.

Revert that hunk; only the preset's own cluster id moves to 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:05:25 +01:00
Egor Rachkovskii 342a965370 test(e2e): port remaining wrapper tests from the interop repo (#4077)
* test(e2e): port remaining wrapper tests from the interop repo

The wrapper suite that moved into tests-e2e (#4027) was a reworked subset of
the one still living in logos-delivery-interop-tests. Comparing both sides
showed 21 tests here against 46 there, with no overlap in the delta: the 25
missing tests cover scenarios the reworked set never included.

Ports those 25 tests, bringing the in-repo suite to the full 46:
  - 12 send scenarios: s01 (nil/destroyed handle), s03, s04, s05, s11, s13,
    s16, s18 (both orderings), s25, s29
  - 7 channel lifecycle tests (rc01-rc04)
  - 6 wrapper corner cases: auto port allocation, MyBoundPorts, ENR

Supporting changes the ported tests need:
  - wrapper_helpers: get_node_tcp_port, get_node_bound_ports, enr_udp_port
  - WrapperManager: channel_create/send/close, destroy_keep_ctx
  - vendored binding refreshed to the revision exposing the channel API
    (additive only; cffi resolves symbols lazily, so nothing existing moves)

Two Edge senders were fixed while porting. build_node_config defaults relay
and store to True, and the flat-JSON config path applies mode=Edge before
explicit fields, so those defaults win: the Edge nodes in s11/s16/s25 came up
as relay and store servers and exercised the relay path instead of lightpush.
They now set relay=False and store=False, matching test_send_e2e_part2. s16
also dropped lightpush=True, which mounts the lightpush server and fails node
start once relay is off; the lightpush client mounts unconditionally.

Suite goes from 21 to 46 functions (53 collected). The docker subset grows
from 3 to 5 as s11 and s25 need a store peer. Local run against a freshly
built library: 45 passed, 2 skipped, 1 xfailed.

* test(e2e): enable autosharding in the channel lifecycle tests

channel_create subscribes to the channel's content topic since #4081, and
resolving that topic to a shard needs autosharding. build_node_config leaves
numShardsInNetwork at 0 and cluster 198 has no preset, so these nodes came up
with static sharding and every channel_create failed with "autosharding is not
configured; pass an explicit shard".

Adds numShardsInNetwork=1 to the six tests that create a channel, matching what
every other wrapper test that touches the send or channel API already does.
rc02 is left alone: channel_send rejects on the id lookup before any shard is
resolved.

Verified locally against a fresh build: the five tests that complete now pass
and the error string is gone from the run.
2026-08-10 08:36:28 +01:00
Gabriel Cruz 4a85db1b6a feat(ffi)!: migrate liblogosdelivery to the nim-ffi 0.3.0 typed C ABI (#4082) 2026-08-06 23:53:38 -03:00
Fabiana Cecin 13d9b52f4a fix(ci): misc CI fixes (#4107) 2026-08-06 22:43:51 -03:00
NagyZoltanPeterandClaude Fable 5 e8566db8ee fix(persistency): own Persistency per node instead of a process-global singleton (#4109)
* fix(persistency): own Persistency per node instead of a process-global singleton

The Persistency singleton (gPersistency) was allocated on whichever FFI
thread first ran waku.start; under --mm:refc its memory belonged to that
thread's heap, so a second library context adopting it read a foreign
heap (SIGSEGV in sdsPersistence -> openJob -> tables.rawGet), one
context's stop stole the other's SDS persistence, and a destroyed
context poisoned re-init with a different local-storage-path.

- remove the singleton (instance/reset/gPersistency); Persistency.new is
  the public constructor, no lock needed (instances are thread-confined)
- own the instance as Waku.persistency: created and provided in
  waku.start, cleared and closed in waku.stop on the owning thread
- expose it via a sync GetPersistency RequestBroker scoped to the node's
  BrokerContext; sdsPersistence resolves through it (same-thread ref
  return, no marshalling)
- add InMemoryStoragePath (":memory:") support: private in-memory SQLite
  per job worker, for tests
- rewrite test_singleton as per-instance + broker coverage; rewrite
  test_thread_affinity from a known-failing UB repro into a regression
  guard (two in-memory jobs, worker spinup/teardown, cross-thread broker
  denial) and register it in test_all; migrate remaining tests to
  new/close; the FFI lifecycle test's stop-steals and different-paths
  cases now pass against the real dylib

The FFI destroy-without-stop teardown gap remains tracked in #4108.

Fixes #4103

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(persistency): create the instance only after startup fully succeeds

waku.start has many error return paths; creating Persistency early meant
every one of them left the field set and the GetPersistency provider
installed with no teardown. Persistency.new is inert (no threads or
files until the first openJob) and every consumer runs post-start, so
creating and providing it as the last startup step removes the need for
any error-path cleanup entirely.

Addresses PR #4109 review feedback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(persistency): create early for startup-stage restores, tear down on failed start

Creating the instance as the last startup step made it impossible for
any stage of start to restore persisted data (e.g. a future store-state
restore). Restore the original ordering -- create and provide the
instance first -- and cover every error return path of waku.start with a
success-flag defer that clears the provider and closes the instance.
Teardown is factored into closePersistency, shared by stop and the
failed-start path.

Addresses PR #4109 review discussion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(persistency): address remaining Copilot review findings

- document that Persistency instances are thread-confined (not
  thread-safe) on the type itself, pointing at the GetPersistency broker
  as the sanctioned access path
- use tryRemoveFile for the FFI test's log cleanup so an unremovable
  file cannot fail the test for unrelated reasons

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix comment

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 23:35:10 +02:00
Tanya S a5d781887e Add logging policy (#4104)
* Add a doc defining logging policy for contributors

* Clarify wording and add msg send/rcv as INFO
2026-08-05 09:37:10 +02:00
Tanya SandClaude Opus 4.8 6a1af006b1 feat: attach and refresh RLN proofs in the send service (Stream B) (#4069)
* feat: attach RLN proofs at the SendService transmission stage

The relay send path published without an RLN proof: proof generation
lived client-side in (legacy)lightpushPublish, so messages dispatched
through SendService -> RelaySendProcessor reached the network unproven
and would be rejected by an RLN-enforcing relay.

Adds Waku.attachRlnProof in the waku/api publish surface and calls it
from SendService immediately after admission, in both send() and the
retry loop. Placement is load-bearing:

- After admit(), so a message rejected by the rate limiter never draws
  a nonce.
- At transmission rather than API entry, because a proof binds to the
  epoch current when the message goes out, and a task can be retried
  for up to MaxTimeInCache after send() returns.

attachRlnProof is a no-op without RLN mounted (message passes through
unproven, as today) and short-circuits on a message that already
carries a proof, so retrying a task neither redraws a nonce nor
changes the bytes. It uses generateRLNProofWithRootRefresh rather than
the plain generator: a task can wait in the task cache while the group
root moves on chain, so the proof is validated against the
acceptable-root window and regenerated once against a refetched merkle
path if it went stale.

Proof-generation failure parks the task as NextRoundRetry rather than
failing it, matching the admission path: the dominant failure is
NonceLimitReached (RLN's own per-epoch budget exhausted), which the
service loop resolves as the epoch rolls over.

Adds tests/messaging/test_rln_proof_attach.nim covering the unmounted
pass-through, attach when mounted, and the idempotency contract that
the retry loop depends on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: handle RLN publish rejections in the send service retry loop

An RLN-invalid publish rejection now recovers through the send
service's existing retry loop instead of an inline retry in the kernel.

When a relay or lightpush publish is rejected as RLN-invalid, the
processor clears the message's stale proof, schedules a background
merkle-proof refresh, and parks the task as NextRoundRetry. The next
loop round re-admits the task and regenerates the proof against the
refreshed path. Clearing the proof is required: attachRlnProof
short-circuits on a message that already carries one, so without the
clear the task would resend the rejected proof until it ages out. The
relay processor previously failed such tasks outright, with no
recovery.

Kernel changes supporting this:

- Remove runRlnRefreshRetry from legacyLightpushPublish. The legacy
  path now schedules the refresh and returns the error tagged with
  RlnProofRefreshScheduledMsg, matching the non-legacy path; retrying
  is the caller's decision. Drops the now-unused
  RlnMerkleProofRefreshTimeout.
- generateRLNProofWithRootRefresh reuses the nonce drawn for the first
  attempt when it regenerates after a stale root, rather than drawing a
  second. Only the merkle path differs between the two attempts, so a
  redraw would spend two message ids from the epoch budget on a single
  message and drift the rate limit manager's accounting away from the
  nonce manager's.

Adds Waku.isRlnRejection / Waku.onRlnProofRejected as the messaging
layer's handle on the kernel's RLN rejection detection and background
refresh. Updates the legacy lightpush tests to the schedule-refresh
contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: cover currentRlnEpochQuota (mounted and unmounted)

currentRlnEpochQuota ships in the enforcement PR, but its mounted-RLN
assertion needs the anvil-backed group-manager scaffolding that lives in
this file, so the coverage rides along here: none when RLN is unmounted,
and the epoch index + userMessageLimit when it is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: consolidate the RLN-rejection parking into parkForRlnProofRefresh

The relay and lightpush processors duplicated the RLN-rejection recovery
(schedule background refresh, clear the stale proof, reset admission,
park as NextRoundRetry); both now call parkForRlnProofRefresh in
send_processor, so the proof-clear the retry contract depends on cannot
drift between the two processors. Also resets firstAdmittedTime so the
regenerated proof's fresh nonce is re-admitted rather than sent uncharged.

No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: clarify that lightpush reuses an already-attached RLN proof

The old wording ("attaches an RLN proof per attempt") reads as if every
retry redraws a nonce. The flow proves a message only when it carries no
proof, so a task admitted once reuses its proof and nonce across retries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: retry once on scheduled RLN proof refresh in the lightpush REST handlers

The kernel lightpush publish paths no longer retry an RLN-invalid publish
inline. On a stale merkle root they schedule a background cache refresh and
return early, tagging the error with RlnProofRefreshScheduledMsg — retrying
is the caller's decision, so the send service recovers through its own loop
and the kernel exposes mechanism only.

The synchronous REST endpoints have no such loop: they call publish once and
map the result to an HTTP status. Left unchanged, a transient stale-root
rejection that the kernel previously absorbed via runRlnRefreshRetry would
now surface to the HTTP client as a 503. Restore the transparent retry where
it belongs under this layering — at the caller — instead of back in the
kernel where it would re-nest inside the send service's retry.

Both the legacy and v3 handlers now retry the publish exactly once when the
first result carries RlnProofRefreshScheduledMsg, under the same
FutTimeoutForPushRequestProcessing bound. The handler's message carries no
proof, so the retry regenerates against the refreshed merkle path. Any other
error, and any error on the retry itself, maps to its response as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: retry RLN proof attach on a charged-but-unproven task

admitAndProve set firstAdmittedTime before attaching the proof, then
guarded its whole body on firstAdmittedTime.isSome(). A transient proof
attach failure (e.g. NonceLimitReached) left the task charged but with an
empty proof, and the next round's early-return skipped the attach entirely
and shipped the message bare. The docstring's own invariant — "once
admitted, a task keeps its slot and its proof" — was violated: it kept the
slot but not the proof.

Guard only the rate-limit charge on firstAdmittedTime, not the attach.
attachRlnProof is already idempotent (short-circuits when RLN is unmounted
or a proof is present), so it is safe to call every round: a charged-but-
unproven task retries the attach until it sticks, then short-circuits. The
ordering invariant holds (charge strictly before attach, so an over-budget
message never draws a nonce), the charge stays once-per-task, and NO_PEERS
retries remain free.

This also removes a latent relay double-charge: previously a bare message
reached the relay, was rejected as RLN-invalid, and parkForRlnProofRefresh
reset firstAdmittedTime — re-charging a slot on the next round. The message
now never leaves unproven.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 10:15:43 +02:00
Gabriel Cruz 4809b991df chore(deps)!: bump nim-ffi to 0.3.0-rc.1 (per-listener event ABI) (#4070) 2026-07-31 13:50:59 -03:00
Fabiana Cecin 230417e7b6 fix: stop host OpenSSL from hijacking bundled BoringSSL in liblogosdelivery (#4085) (#4086)
This commit is a workaround to a nim-boringssl issue; see: https://github.com/logos-messaging/logos-delivery/pull/4086#issuecomment-5140765772
2026-07-31 08:27:29 -03:00
Igor SirotinandClaude Fable 5 f8b036594e fix(conf): default QUIC off on the messaging path (#4084)
The structured path was the only place defaulting quicSupport to true
(kernel default is false). Booting that QUIC listener SIGSEGVs in
lsquic setupSSLContext, so keep QUIC opt-in until the transport is
validated.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 21:34:49 +01:00
Igor SirotinandClaude Fable 5 ba28e32e58 feat(conf): add localStoragePath to MessagingClientConf (#4083)
The structured shapes had no way to set the node's storage directory:
localStoragePath is kernel-only and the structured parser rejects bare
kernel fields. Hosts (logos-delivery-module) inject a per-instance path
and need a place the structured grammar accepts.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 21:32:35 +01:00
Igor SirotinandClaude Opus 4.8 504336efdb feat(node-info): expose MaxMessageSize node info item (#4018)
* feat(node-info): expose MaxMessageSize node info item

Add a `MaxMessageSize` entry to `NodeInfoId` so the max message size
(in bytes) accepted by the relay protocol can be queried through the
node info surface, consumed by both the debug API and the FFI
`get_node_info` / `get_available_node_info_ids` calls.

The value is read from the mounted relay's `maxMessageSize` and is empty
when the relay protocol is not mounted on the node (e.g. light nodes),
mirroring the existing `MyMixPubKey` handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(node-info): source MaxMessageSize from node config

Read the reported value from `conf.maxMessageSizeBytes` instead of the
mounted relay. The relay only holds a copy of the configured value and is
absent on relay-disabled (light) nodes, which would report an empty
string. Sourcing from the config makes the item always available and
reflects the node's configured limit directly.

WakuStateInfo now holds the WakuConf it reports on; init takes it as an
argument.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 17:06:49 +01:00
Darshan 3288d2d88d fix: subscribe to the channel's content topic on channel_create (#4081) 2026-07-30 17:01:15 +01:00
Igor Sirotin afe90a9d31 chore(metrics): sync fleet dashboards from Grafana (#4079)
Refreshed the two shared-Grafana dashboards and re-applied the old-or-new metric
name union from #4074, so the panels keep working across the rename.

  waku-fleet-dashboard.json         v181 -> v191
    drops the five deprecated Store v2 panels; the "Store V3 Times wire-to-wire"
    query moves to sum without (phase) and changes() instead of deriv(), which is
    the better staleness filter for a gauge that moves and returns
  waku-single-node-dashboard.json   v7 -> v9
    no functional change, a Grafana 12 re-save (pluginVersion, threshold base,
    schemaVersion 40 -> 41)

The $dc variable is populated from libp2p_peers rather than waku_version as the
export had it: libp2p_* comes from a dependency and is immune to our renames, so
it cannot rot the next time metrics are renamed. Grafana keeps variable queries
outside `expr`, so the #4074 union pass never saw this one.

Store/Archive is collapsed again, matching the other rows; the export had it open
because of whoever last hit save. "Store v3 query request rates" moves to x=0 to
fill the space left by deleting its v2 sibling.

The dashboards under apps/ are deliberately not synced. They are provisioned into
the docker-compose stacks that ship beside them (docker-compose.yml mounts
monitoring/configuration/dashboards into /var/lib/grafana/dashboards), so they
must match the local datasources.yaml and prometheus-config.yml, not status.prod.
The Grafana copies carry a datasource uid that the local stack does not provision
and job selectors that its single scrape job cannot match.
2026-07-30 16:42:39 +01:00
Tanya SandClaude Opus 4.8 c873f74b4b feat: enforce the per-epoch rate limit at the send service (RLN-sourced) (#4062)
* feat: enforce per-epoch budget in RateLimitManager.admit

Replaces the pass-through skeleton with a lazily rolled fixed window:
admit() charges one message against the current epoch, resets the
counter once epochPeriodSec has elapsed, and rejects with OverBudget
when messagesPerEpoch is exhausted. Disabled or non-positive
configurations admit everything, so the default-constructed
MessagingClientConf (enabled = false) keeps today's behaviour.

Parking of over-budget messages stays with the SendService scheduler
(NextRoundRetry); the manager only answers whether one more
transmission fits. The queue / dequeueReady stubs that anticipated
manager-side parking are removed accordingly.

Extends tests/messaging/test_rate_limit_manager.nim with budget
boundary, epoch rollover, resetEpoch, and degenerate-config cases,
replacing the enabled-pass-through placeholder test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: split rate limit manager into config, quota source, and enforcement modules

Decomposes logos_delivery/messaging/rate_limit_manager/ into three
modules with one responsibility each:

- rate_limit_config: the configuration vocabulary (RateLimitConfig,
  RateLimitError, defaults, isEnforcing). The API conf layer now
  imports this alone instead of the enforcement engine.
- quota_source: the RLN seam. A single QuotaProvider callback returns
  EpochQuota (epoch index + user message limit) so the two are read
  atomically and a read cannot straddle an epoch boundary. Returns
  none when RLN is unavailable, selecting the wall-clock fallback. The
  callback shape keeps the manager free of any dependency on the Waku
  kernel; the RLN-backed provider is built one layer up.
- rate_limit_manager: enforcement only; re-exports the other two so
  existing single-import call sites are unchanged.

Config field names now mirror RLN exactly, per the requirement that the
rate limit share RLN Relay's format: epochSizeSec (was epochPeriodSec)
and userMessageLimit (was messagesPerEpoch), both uint64 to match
RlnConf. Renaming is contained to this branch: the config type moved
into the new rate_limit_config module here.

admit() now works in epoch-index terms: the epoch comes from the
provider when set (RLN's calcEpoch value), else from an absolute
wall-clock window (unixTime div epochSizeSec — absolute rather than
anchored at first use, matching RLN's derivation). The effective limit
is min(config.userMessageLimit, RLN's) — RLN can only tighten the
configured limit, since exceeding it would fail at proof generation
once the epoch's message ids are exhausted.

With no provider wired (this commit), behaviour is the wall-clock
window as before; RLN-backed provider wiring follows separately.

Tests rewritten against injected fake providers: limit boundary, epoch
rollover without sleeps, RLN-clamps-config, config-tightens-below-RLN,
and the wall-clock fallback (7 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: charge admission once per task and exempt budget-parked tasks from the reaper

Two defects the send-service seam had once admission actually enforces,
both fixed by a single DeliveryTask.firstAdmittedTime field:

- The retry loop gated admission on firstPropagatedTime.isNone(), so a
  task that failed to propagate (e.g. no peers) was re-admitted on every
  1s tick, re-charging the epoch budget and — with the shipped default of
  one message per epoch — starving all other traffic. Admission is now
  gated on firstAdmittedTime.isNone(): a task charges one slot / draws one
  nonce for its lifetime, and retries reuse it.

- reportTaskResult reaped any never-propagated task older than
  MaxTimeInCache (60s) measured from message creation, so an over-budget
  task parked for a 600s epoch was hard-failed with a misleading "Unable
  to send within retry time window" long before the epoch could roll
  (issue #4049). The reaper now runs only for admitted tasks and measures
  from admission, so a task waiting for epoch budget is exempt and gets a
  full delivery window once it is finally admitted.

The RLN-rejection branch in both processors resets firstAdmittedTime
along with clearing the proof, so the regenerated proof's fresh nonce is
re-admitted rather than sent uncharged.

The reap decision is extracted to DeliveryTask.isDeliveryTimedOut and
unit-tested (tests/messaging/test_delivery_task_reaping.nim); a
scheduler-level integration test of park-and-release is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: source the rate limit manager's epoch and budget from RLN

Wires the quota seam to its producer, so enforcement tracks RLN rather
than only the wall clock.

- Waku.currentRlnEpochQuota (waku/api/publish) reads RLN's current epoch
  index and the epoch's user message limit together, returning none when
  RLN is not mounted (or its limit is unset).
- MessagingClient.new builds a QuotaProvider closure over that accessor
  and hands it to the RateLimitManager. The closure is late-binding: it
  queries the kernel on each admission, so a node whose RLN mounts after
  construction upgrades from the wall-clock fallback to RLN's epoch and
  limit automatically, with no reconstruction.

With this, admit() rolls its window on RLN's epoch and clamps the
configured cap to RLN's user message limit; without RLN it still falls
back to the absolute wall-clock window and the configured limit.

Also switches the quota seam from std/options to results `Opt`, matching
the kernel surface it now bridges (`groupManager.userMessageLimit` is
`Opt`) and the rest of the messaging layer post-Opt migration.

Tests: currentRlnEpochQuota is none unmounted and reports epoch + the
configured userMessageLimit when mounted (anvil-backed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: cover admit-once and park/release at the send service scheduler

The rate limit manager's budget logic and the delivery-timeout reaper are
unit-covered, but the send service's use of them across the service loop was
not. Drive the scheduler a tick at a time against a scripted fake processor
and a fixed-epoch quota provider — no network, no sleeps — asserting:

- a task is charged against the budget exactly once however many rounds
  delivery takes (firstAdmittedTime guards re-admission);
- an over-budget task parks as NextRoundRetry without reaching the processor,
  then is admitted and delivered on the first tick after the epoch rolls.

Two testability seams keep this deterministic without a live relay: an
optional sendProcessor override on SendService.new injects the fake, and
trySendMessages is exported to drive one loop tick. The task is built
directly (like test_delivery_task_reaping) since DeliveryTask.new resolves
its shard through a broker provider only registered once the node starts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: note rateLimit config is settable only programmatically

MessagingClientConf.rateLimit cannot be set through the JSON config or a CLI
flag: it carries no name pragma, and RateLimitConfig is a nested object with
no parseCmdArg, so applyJsonFieldsToConf rejects it with "cannot be set via
JSON". Record the limitation and the fix path on the field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: consolidate the admission gate into admitOnce

send() and the retry loop duplicated the admit-then-stamp-firstAdmittedTime
sequence; both now call SendService.admitOnce, keeping the charge-once
invariant in one place.

No behavior change; messaging tests pass (14/14).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* use explicit return statement

* Make comments mroe concise

* refactor: give each rate-limit config field a single responsibility

`isEnforcing` folded three fields into one "should I enforce" answer, so a
zeroed `messagesPerEpoch` or `epochPeriodSec` silently disabled an enabled
config. Split the responsibilities:

- `enabled` alone gates enforcement; `admit` reads it directly and
  `isEnforcing` is removed.
- a zero `messagesPerEpoch` now means what it says — admit nothing — which
  `admit` already yields (`0 >= 0` -> OverBudget), no special case.
- `RateLimitManager.new` returns a Result and rejects an enabled config with
  `epochPeriodSec == 0`, the only value that could crash the wall-clock
  fallback (`unixTime div epochPeriodSec`).

Callers thread the Result through; a zero-init `RateLimitConfig` stays valid
(disabled), so default construction is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 13:10:41 +02:00
Darshan ed8e881c1d fix: stop emitting receive events after channel_close (#4075) 2026-07-30 01:05:53 +05:30
Igor Sirotin 8125cd0262 refactor(metrics): give every metric a logos_delivery_ prefix (#4074)
* refactor(metrics): prefix node metrics with logos_delivery_

Every metric the node exports now starts with logos_delivery_. There was no
prefix mechanism before: nim-metrics derives the exported name from the Nim
identifier, no declaration passed an explicit `name = "..."`, and the waku_
convention was maintained by hand -- 69 of the 80 node metrics followed it and
11 did not (query_count, query_time_secs, event_loop_load,
event_loop_accumulated_lag_secs, postgres_payload_size_bytes, reconciliation_*,
total_* and the camelCase rendezvousPeerFoundTotal).

Identifiers are renamed rather than given a `name = "..."` argument, keeping the
invariant that the Nim identifier is the exported name and letting the compiler
check every call site.

rendezvousPeerFoundTotal becomes logos_delivery_rendezvous_peer_found: it was the
only camelCase metric, and the trailing Total was redundant since nim-metrics
already appends _total to counters at exposition time.

library/ is untouched on purpose -- `proc waku_version()` in
kernel_api/debug_node_api.nim is the exported libwaku C ABI symbol, not the gauge
of the same name in node_telemetry.nim.

BREAKING CHANGE: metric names change. Dashboards, alert rules and recording rules
that reference waku_* must be updated; see docs/operators/how-to/monitor.md.

* refactor(metrics): prefix auxiliary app metrics with logos_delivery_

Applies the same prefix to the tools shipped from this repo: liteprotocoltester
(lpt_*), networkmonitor (networkmonitor_*), chat2bridge (chat2_*) and the
lightpush_mix example (lp_mix_*).

These tools are not the delivery node and already had their own consistent
prefixes, so this commit is separable from the node rename if the intent was to
namespace only the node itself.

* chore(metrics): query old and new metric names in Grafana dashboards

212 expressions across 9 dashboards now match both the waku_* and the
logos_delivery_* spelling, so panels keep working across the upgrade and over
historical data:

  sum by (type)((increase(waku_node_errors_total{...}[$__rate_interval])
              or increase(logos_delivery_node_errors_total{...}[$__rate_interval])))

The `or` is placed around the leaf, inside every aggregation. That depth is
load-bearing: `or` keeps its right operand only for label sets absent from the
left, so `sum by (type)(old) or sum by (type)(new)` aggregates each half of the
fleet separately and then discards the right one entirely -- silently dropping
every already-upgraded node. 36 panels here collapse `instance`.

Measured against a local Prometheus scraping two targets, one exporting old names
at 10/s and one exporting new names at 20/s (truth 30/s): union outside the
aggregation gives 10, union around the leaf gives 30.

Where the leaf sits in a range vector the whole call is duplicated, since
`(a or b)[5m]` is not valid PromQL.

Once every scraped node runs a release with the new names and the old samples
have aged out of retention, the `or` half can be deleted.

* test(e2e): expect logos_delivery_-prefixed metric names

The e2e suite asserts against a live /metrics endpoint, which serves only the new
names, so these are replaced rather than unioned. libp2p_* entries are unchanged.

* docs(operators): document the logos_delivery_ metric prefix

Records that every metric the node exports is prefixed, that dependency metrics
(libp2p_*, nim_gc_*, process_*) keep their own names, and shows where the `or`
has to sit if operators maintain their own dashboards or alert rules.

* refactor(metrics): name the store fleet metrics after store, not relay

logos_delivery_relay_fleet_store_msg_size_bytes and _msg_count are declared in
waku_store/protocol_metrics.nim and recorded by the store client, but carried a
relay prefix. Renamed to logos_delivery_store_fleet_msg_size_bytes and
logos_delivery_store_fleet_msg_count.

The dashboard keeps matching the old exported name, which was
waku_relay_fleet_store_*.

Note that both metrics are wrong independently of their name, see the PR
description.
2026-07-29 18:56:50 +01:00
Egor Rachkovskii 26e1d62524 ci: run docker subset of the API/e2e wrapper tests (#4052)
* Add in-repo API/e2e test suite (tests-e2e)

Migrate the liblogosdelivery API/wrapper pytest suite into tests-e2e/ and run it in CI (e2e-api-tests.yml) against the built library. Edge senders use the real lightpush path (relay=False).

* ci: run docker subset of the API/e2e wrapper tests

Parametrize e2e-api-tests.yml with `subset` (non-docker|docker) and `node_image` inputs, and add a send-api-e2e-docker-tests job that runs the docker_required wrapper tests (S19/S20/S31) against the built node image.

The docker job needs both the liblogosdelivery artifact and the container-image build, and skips when no image is available (fork PRs lack the registry secrets). The non-docker subset still needs only `build`, so a docker-image build failure cannot mask its result.

* ci: run docker e2e subset on fork PRs against a pinned public image

Drop the gate that skipped the docker subset when no PR-built node image
is available. Fork PRs lack the quay secrets, so build-docker-image
succeeds with an empty output and the subset now falls back to
DEFAULT_NWAKU instead of being skipped.

Pin DEFAULT_NWAKU to wakuorg/nwaku:v0.38.1. The rolling :latest tag is a
daily upstream master build and currently fails to boot with
"updateAddressInENR: Public key does not correspond with given private
key", which would have made the subset permanently red on fork PRs.
s31 passes against v0.38.1 and against the PR-built image.

Echo the resolved peer image at the start of the test step. The job name
is left static so the check can still be made required.
2026-07-29 13:31:26 +01:00
Darshan b5f624f519 fix: report the wire sender's id in channel_message_received (#4073) 2026-07-28 23:34:49 +05:30
a7df0d9c56 chore: adjust artifact builds and uploads to contain logosdeliverynode app (#4059)
* feat(nix): add logosdeliverynode build target

Generalize the wakucanary-only binary path in nix/default.nix into an
app-target map so any app binary shares one build/install/rln-bundle
path, and expose a `logosdeliverynode` flake package (with gitVersion
for --version reporting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(github): build logosdeliverynode in release/pre-release/windows workflows

- release-assets.yml: build logosdeliverynode (POSTGRES=1) so it ships in
  the waku-<arch>-<os>.tar.gz release asset.
- pre-release.yml: add logosdeliverynode to the make targets and to the
  nwaku nightly/RC tarball.
- windows-build.yml: build logosdeliverynode.exe and assert it exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(jenkins): build & push logosdeliverynode docker image to harbor

Jenkinsfile.release now builds a second image from the dedicated
logosdeliverynode Dockerfile stage and pushes it to
harbor.status.im/wakuorg/logosdelivery:<IMAGE_TAG> alongside the primary
image. Add a logosdeliverynode stage to the root Dockerfile so the image
entrypoint is /usr/bin/logosdeliverynode rather than the generic
/usr/bin/wakunode symlink. Bump the pipeline timeout to 40m to cover the
extra compile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-28 11:20:05 +01:00
Darshan 92841b5041 fix: bound the error label of failed_store_queries (#4053) 2026-07-28 14:24:51 +05:30
Fabiana Cecin 296460a407 fix: dial QUIC before TCP regardless of address list order (#4061) 2026-07-24 09:41:35 -03:00
NagyZoltanPeter 29861bc286 chore: bump nim-brokers to 3.3.0 and add nim-brokers skill (#4058)
* chore: bump nim-brokers to 3.3.0 and add nim-brokers skill

Bump brokers requirement v3.1.4 -> v3.3.0 (nimble, lock, nix/deps.nix)
and add the nim-brokers Claude skill copied from nim-brokers master
(doc/CLAUDE_brokers_skill.md) at .claude/skills/nim-brokers/SKILL.md.

* ci(container-image): serialize wakunode2 and logosdeliverynode builds
2026-07-24 07:22:22 +02:00
Egor Rachkovskii 8ad99f10f4 Add in-repo API/e2e test suite (tests-e2e) (#4027)
Migrate the liblogosdelivery API/wrapper pytest suite into tests-e2e/ and run it in CI (e2e-api-tests.yml) against the built library. Edge senders use the real lightpush path (relay=False).
2026-07-23 11:06:01 +01:00
Tanya SandClaude Opus 4.7 54360d47bc Move rate-limit-manager to messaging client layer (#4021)
* chore: drop rate-limit stage from reliable channel send pipeline

Collapses the outgoing pipeline to `segmentation -> sds -> encryption ->
dispatch` by folding the encrypt-and-dispatch tail of `onReadyToSend`
directly into `send()`. Removes the `RateLimitManager` field, its
constructor param, the `ReadyToSendEvent` listener, and the
`awaitingDispatch` accounting that only existed to bridge the event-bus
hop between `send()` and `onReadyToSend`.

The `rate_limit_manager.nim` module itself is untouched — it will be
relocated to the messaging layer (co-located with RLN) in a follow-up
commit, where per-epoch admission actually belongs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: relocate RateLimitManager to messaging layer

Moves rate_limit_manager.nim from `logos_delivery/channels/` to
`logos_delivery/messaging/rate_limit_manager/` — the correct owner
now that admission sits alongside RLN in the messaging layer instead
of being fanned out to per-channel event listeners.

The API surface changes:

- `enqueueToSend` + `ReadyToSendEvent` (broker-based fan-out to a
  ReliableChannel listener) is replaced by `admit(msg): Future[Result[
  void, RateLimitError]]`. Callers now branch directly on the result
  instead of subscribing to an event.
- `channelId`, `SdsChannelID` and the SDS import are dropped — the
  messaging layer has no notion of channels; SDS was a channels-layer
  concern that only survived on this type because of the old broker
  fan-out.
- `brokerCtx` is dropped for the same reason.

Epoch config (`epochPeriodSec`), the wall-clock `currentEpochStart`,
`queue`, `dequeueReady`, and `resetEpoch` are preserved exactly as the
original owner designed them — this refactor is intentionally scoped
to the API surface, not the epoch mechanism.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: meter SendService transmissions through RateLimitManager

Wires the relocated RateLimitManager into the messaging layer at the
transmission stage rather than the API entry point:

- MessagingClientConf gains a rateLimit: RateLimitConfig field
  (defaulting to DefaultEpochPeriodSec / DefaultMessagesPerEpoch), and
  MessagingClient.new hands the constructed manager to SendService.
- SendService consults admit() before the first transmission of a
  task, both in send() and in the retry loop. Re-publishes of an
  already-propagated message (firstPropagatedTime set) skip admission:
  they resend the same bytes, which reuse the same RLN proof/nullifier
  and consume no fresh epoch slot.
- An over-budget task parks in the task cache as NextRoundRetry; the
  service loop re-admits it as the epoch budget frees up. The skeleton
  admit() is a pass-through, so behaviour is unchanged today.

Gating transmissions instead of MessagingClient.send keeps SDS repair
rebroadcasts free of API-entry rejection (SDS decides that a repair is
needed; the transmission scheduler decides when it fits the budget)
while every wire transmission still draws from one node-wide budget --
which network-side RLN enforcement applies to repairs regardless of
any local bypass. It is also where RLN proof attachment must happen,
since proofs bind to the epoch current at transmission time.

Adds tests/messaging/test_rate_limit_manager.nim covering the current
disabled + enabled pass-through behaviour, wired into
all_tests_waku.nim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: make MessagingClientConf.rateLimit reachable, warn on dead channel knobs

Addresses two review findings on the move PR:

- `rateLimit` was a plain `RateLimitConfig`, but `merge` only copies
  `Opt` fields (`when oField is Opt`), so every override path
  (`LogosDeliveryConf.init`, JSON `messagingOverrides`) silently dropped
  it and the field was always taken from `base` — unsettable by any
  caller. Make it `Opt[RateLimitConfig]` like every other field;
  `MessagingClient.new` falls back to `DefaultRateLimitConfig` (new
  const, rate limiting disabled) when unset. Adds a merge test.

- The channel-level knobs (`rateLimitEnabled` /
  `rateLimitEpochPeriodSec` / `rateLimitMessagesPerEpoch`) are still
  parsed but unread since rate limiting moved to the messaging client,
  so setting them was silently ignored. `ReliableChannelManager.new`
  now logs a deprecation warning when any is set. Full removal remains a
  follow-up API decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-22 14:38:33 +02:00
Ivan FB 2dbf9a3cb5 fix(channels): default channels to unencrypted so messages flow (#4051) 2026-07-21 00:00:36 +02:00
9827be5990 feat: logos_delivery_node app + messaging REST API with event observability (#4014)
* WIP logosdeliverynode app initial commit

* WIP - extra cli option

* WIP: messaging client REST endpoints

* Add event poll for messaging rest with cache mechanism

* Messaging rest tests

* test: assert 404 via raw string client in messaging REST test

presto's typed REST client raises RestDecodingError when it cannot decode
a non-2xx text error body into the response type. Add a RestResponse[string]
stub (messagingGetSendEventsByIdRawV1) and point the "already-polled id ->
404" assertion at it, matching the relay REST test pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* remove customized cli args as confutils has no support for it

* Introduce --entry-layer and re-introduce --mode flags into cli args, applied new driver into LogosDelivery + tests

* Add messaging REST client test

* Add docker image build of logosdeliverynode for CI builds

* Fix tests

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Refactor Messaging REST API to better match Messaging Send and Receive APIs

* chore: migrate messaging REST API to Opt[T]

Follow-up to the rebase onto master's repo-wide Option[T] -> Opt[T] change
(#4035). Converts the code this branch adds to the new convention:

- messaging/rest_api/types.nim: MessagingJsonEnvelope fields to Opt[T],
  Opt.some/Opt.none, and json_serialization/pkg/results instead of
  json_serialization/std/options.
- tests: WakuNodeConf.clusterId is now Opt[uint16]; DTO fields are Opt.

`Option[ContentBody]` in the handlers is presto's own API and stays as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-17 18:14:52 +02:00
Fabiana Cecin ce918b0819 chore: replace Option with Opt (#4035)
* change all usage of std.options.Option[T] to results.Opt[T]
* fix broken apps and examples (to validate refactor)
* removed all std/options code added for libp2p v2 migration
* add broker Opt codec to persistency/backend_comm.nim
* add a readValue overload for Opt[T] in tools/confutils/cli_args.nim
* keep Option where required (Presto, confutils' config_file.nim)
* Change generateRlnProof error handling
* Fix imports
2026-07-16 14:02:17 -03:00
Ivan FB 1c83c32397 feat(channels): add channelExists to reliable channels API (#4045) 2026-07-16 18:38:05 +02:00
Ivan FB 0a19473a3b fix: don't apply reconnect backoff to discovered relay peers (#4029) 2026-07-16 16:56:12 +02:00
Tanya SandClaude Opus 4.8 77cb8a6c7a refresh merkle proof cache reactively on msg publish rejection (#4013)
* refresh merkle proof cache reactively on lightpush rejection

* Add tests for lightpush force refresh regenerates proof

* Fix linting

* Add nonce rollback for lightpush publish retry

* Fix nonce rollback race: make rollbackNonce CAS-conditional

Two bugs in the previous blind-decrement rollbackNonce():

1. Concurrent draw: another proof generated between attempt 1 and the retry
   advances nextNonce, so decrementing steals a nonce already claimed by
   another live message — nullifier collision, on-chain slashable.

2. Pre-attached proof: if the incoming message already carries a proof,
   checkAndGenerateRLNProof short-circuits and draws no nonce, yet the retry
   still called rollback, stomping on whatever nonce the preceding message
   legitimately drew.

Fix: rollbackNonce now takes the specific nonce as a parameter and only
decrements when nextNonce == nonce + 1. checkAndGenerateRLNProof returns
(msg, messageId) so callers can thread the drawn id through to the retry
as reuseMessageId; generateRLNProof no longer performs an implicit rollback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Extend reactive Merkle proof refresh to REST relay and broker paths

With PathCheckMinInterval removed, a non-empty merkleProofCache is trusted
forever unless force=true is passed. Previously only the two lightpush retry
paths ever passed force=true; the REST relay publish handlers (static and
auto-sharding) and the broker proof provider had no rejection feedback loop,
so a sliding root window would permanently reject their messages until restart.

- REST relay handlers: run validateMessage first; if it returns
  RlnValidatorErrorMsg, force-refresh the cached path and retry validation once
  before publishing — mirroring the lightpush reactive pattern.
- Broker provider (rln.nim): decode the generated proof bytes, call
  validateRoot on the embedded Merkle root, and force-refresh + regenerate
  when the root is outside the acceptance window — since the broker has no
  external rejection signal to react to.

Tests added for all three new paths, using the corrupted-cache technique
(all-zero merkleProofCache → garbage root → rejection → retry).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Gate lightpush Merkle refresh on RlnValidatorErrorMsg for 420 responses

420 INVALID_MESSAGE is returned for any validateMessage failure, not just
RLN ones (e.g. oversized messages), so a bare status-code check triggers
an unbounded on-chain fetchMerkleProofElements RPC per rejected message.
Match the legacy-lightpush path: require the error description to contain
RlnValidatorErrorMsg before refreshing; 504 OUT_OF_RLN_PROOF remains
unconditional as it is unambiguously RLN-specific.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix merge conflict residue: replace RlnResult with Result in proof.nim

RlnResult[T] was removed as a type alias in commit 0a1700e23 (replace RLN
specific Result with generic Result). The merge left one stale reference at
proof.nim:62; replace with the equivalent Result[seq[byte], string].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix ResultDefect on success and stale test stubs in lightpush retry path

Two bugs left by a bad merge:

1. lightpush.nim: isRlnRelatedFailure was computed before firstResult.isOk()
   was checked, accessing firstResult.error unconditionally. When publish
   succeeds, this raises a ResultDefect. Reorder the guard so isOk()/isNone()
   short-circuits before the error fields are touched.

2. test_wakunode_lightpush.nim: the 420-retry stubs used descriptions
   ("simulated stale merkle path", "still stale") that don't contain
   RlnValidatorErrorMsg. The code gates 420 retries on that substring to
   distinguish RLN rejections from unrelated INVALID_MESSAGE responses
   (e.g. oversized messages). Update stubs to emit RlnValidatorErrorMsg so
   the retry path fires as the tests expect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix linting

* Dedupe checkAndGenerateRLNProof force-refresh test

Same test existed in both test_wakunode_lightpush.nim and
test_wakunode_legacy_lightpush.nim. It exercises the checkAndGenerateRLNProof
primitive (short-circuit override on force=true + nonce rollback +
cache refetch), not lightpush-specific behavior. The modern lightpush copy
provides full coverage; the legacy copy paid the anvil/on-chain setup cost
for zero incremental coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Bound lightpush RLN refresh-retry with a 5s timeout

The reactive refresh + retry path calls into the RLN contract (eth_call for
getMerkleProof) then republishes over libp2p — neither has an outer bound,
so a hanging RPC endpoint could stall the caller indefinitely. Wrap the
retry in withTimeout(RlnRefreshRetryTimeout); on timeout, log and return
the original error rather than hang. Applied to both the legacy and modern
lightpushPublish retry paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Extract broker RLN proof provider into generateRLNProofWithRootRefresh

The broker proof provider's inline logic — generate proof, decode it,
self-validate the root against the acceptable-root window, and on stale
root force-refresh + regenerate — is a reusable primitive on its own.
Lift it into rln/proof.nim next to the existing generateRLNProof so the
provider closure in mount() collapses to a single call, and any future
non-broker caller can share the same flow.

Pure refactor; behavior identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Extract REST relay handlers' RLN attach+validate+retry into a helper

Both the static and auto POST handlers duplicated the same block: attach an
RLN proof, run wakuRelay.validateMessage, and on RlnValidatorErrorMsg
substring force-refresh the merkle path and revalidate. Lift the block into
attachRlnProofValidateWithRetry alongside a small RlnPublishError variant
so the handlers can keep dispatching 500 for proof-gen failures and 400
for validator rejections. Each handler is now ~15 lines instead of ~35 and
the retry contract lives in one place.

Pure refactor; behavior unchanged. Verified against
tests/wakunode_rest/test_rest_relay.nim (13/13) including the two stale-
RLN-proof retry tests added earlier on this branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Simplify legacy lightpush autosharding path to a direct getShard call

The single-content-topic path went through getShardsFromContentTopics,
iterated the resulting table under the assumption it was 1-entry, and
had a defensive fallthrough (`resolved = false` → bare `return`) for
the empty-table case. That case is unreachable: on success the table
always has exactly one entry, and the fallthrough returned a default-
initialized Result — not behavior worth preserving.

Replace with the same two-step pattern the modern lightpushPublish uses
(NsContentTopic.parse → sharding.getShard) so both handlers share a
shape and the (now-explicit) failure paths surface useful error strings.

Verified against tests/node/test_wakunode_legacy_lightpush.nim (9/9).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Break up legacyLightpushPublish into single-purpose helpers

legacyLightpushPublish had grown to ~100 lines with three concerns
interleaved: choosing between client/self-request publish, resolving the
effective pubsub topic, and running the RLN refresh-retry with timeout.
Lift each into a private top-level proc so the main proc reads top-to-
bottom in ~35 lines: precondition → prepare message + proof → resolve
topic → publish once → RLN-refresh retry if applicable.

The new helpers are:
  - internalLegacyLightpushPublish — client vs self-request dispatch
    (was the inline closure)
  - resolveLegacyPubsubTopic       — explicit param or autosharding
  - runRlnRefreshRetry             — force-refresh proof + retry with
    RlnRefreshRetryTimeout bound; returns caller's fallback on timeout

Nonce now appears in a top-level signature, so import it directly from
rln/nonce_manager rather than re-exporting through rln/proof.nim (the
latter would leak nonce_manager's bulk chronos/times exports and clash
with waku_rendezvous overload resolution).

Pure refactor; behavior unchanged. Verified against
tests/node/test_wakunode_legacy_lightpush.nim (9/9) and
tests/node/test_wakunode_lightpush.nim (11/11).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Replace forceMerkleProofRefresh flag with invalidateMerkleProofCache

The forceMerkleProofRefresh bool was threaded through five procs
(checkAndGenerateRLNProof → generateRLNProof → generateProof base + on-chain
→ ensureFreshMerkleProofPath) purely so a caller could ask the merkle path
to be refetched from chain on a stale-RLN retry. Reading any one of those
signatures required a mental stack trace back up to the retry caller to
know what "force" meant.

Flip the contract: expose invalidateMerkleProofCache on the group manager;
callers invoke it on the retry path to empty the cache, and the normal
proof-gen flow then sees an empty cache and refetches on its own. No flag
crosses procs.

Removed forceMerkleProofRefresh from:
  - group_manager_base.generateProof (+ on_chain override)
  - ensureFreshMerkleProofPath (its `force` param)
  - generateRLNProof

Renamed on checkAndGenerateRLNProof: forceMerkleProofRefresh → regenerate.
It now controls one thing — bypassing the "message already has a proof"
short-circuit — and never propagates downstream. Callers pair it with
invalidateMerkleProofCache when they need the refetch semantics too.

Retry callers updated in lockstep: generateRLNProofWithRootRefresh, REST
attachRlnProofValidateWithRetry, and both legacy + modern lightpush retry
paths now call groupManager.invalidateMerkleProofCache() before regenerating.
The primitive test in test_wakunode_lightpush.nim now exercises the pair
directly, and the onchain ensureFreshMerkleProofPath test is rewritten to
exercise invalidate → ensureFresh as one flow (retiring its `force = true`
variant).

Pure refactor; behavior unchanged. Verified against:
  tests/waku_rln_relay/test_rln_group_manager_onchain.nim   (29/29)
  tests/wakunode_rest/test_rest_relay.nim                   (13/13)
  tests/waku_rln_relay/test_wakunode_rln_relay.nim          (5/5, 1 skip)
  tests/node/test_wakunode_lightpush.nim                    (11/11)
  tests/node/test_wakunode_legacy_lightpush.nim             (9/9)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Reject empty merkle proof cache in on-chain generateProof

An empty cache passes the existing mod-32 length check and silently
produces a witness with no path elements, yielding a proof no validator
will accept. Catch the case explicitly so the failure surfaces at the
callsite rather than after a wasted publish round-trip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Drop nonce reuse on RLN retry; consume a fresh nonce instead

The retry path threaded a rejected attempt's message id back so
NonceManager.rollbackNonce could CAS the counter and reclaim the slot.
This bought at most one nonce per retry (a rare event) at the cost of
an Option[Nonce] param, a tuple return, and a concurrent-draw race the
CAS had to defend against.

Simpler to optimistically waste the nonce: checkAndGenerateRLNProof
returns Result[WakuMessage, string] and callers drop the tuple
destructuring. NonceManager.rollbackNonce and its tests, now dead,
are removed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Split checkAndGenerateRLNProof: extract attachRLNProof

The `regenerate: bool` param was control coupling: every caller passing
`regenerate = true` was a retry path that had already established
`rln.isSome()` (it just called `invalidateMerkleProofCache` on the
group manager) and wanted an unconditional fresh proof, not a "check
and maybe generate".

Extract that into `attachRLNProof(r: Rln, message)`, which always draws
a fresh nonce and rebuilds the proof, replacing any existing one.
`checkAndGenerateRLNProof(rln: Option[Rln], message)` keeps only its
"check" behaviour — short-circuit on an existing proof, pass through
when RLN is not configured — and delegates to `attachRLNProof`.

Taking a concrete `Rln` deletes the previously unreachable
`regenerate = true` + `rln.isNone()` branch (which would have
republished the stale rejected proof) at the type level, and removes
the duplicated nonce-draw/generateProof/encode body that
checkAndGenerateRLNProof carried alongside generateRLNProof.

The two lightpush retry sites and the invalidate+regenerate test now
call attachRLNProof directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Bound only the proof refresh on lightpush RLN retry, not the republish

RlnRefreshRetryTimeout previously wrapped the whole retry leg: merkle
path refetch + proof regeneration + the second publish. Sharing one
clock meant a slow (but successful) refetch could leave no budget for
the publish, cancelling it mid-flight — burning the freshly drawn nonce
and, worse, reporting the original RLN rejection while the retried
request may already have reached the service node and been relayed.

Move the withTimeout to cover only attachRLNProof (the on-chain
eth_call + proof regeneration — the one step with an unbounded external
dependency). The retried publish runs unbounded after it, matching the
first attempt's semantics, so slow transports (e.g. an optional mix
route) cannot be cancelled by the refresh budget and the caller always
receives the publish's actual result.

On refresh timeout the retry publish never starts and the original
rejection is returned, so a "failed" answer now guarantees no second
request is in flight.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Shield the shared merkle path refetch from caller cancellation

ensureFreshMerkleProofPath coalesces concurrent callers onto a single
in-flight refetch future and awaited it directly. Chronos propagates
cancellation into the awaited future, so one caller being cancelled
(e.g. a publish retry whose RlnRefreshRetryTimeout fired mid-refetch)
cancelled the shared future under every coalesced caller — and threw
away an eth_call that may have been about to complete.

Await the in-flight future via join() instead: cancelling a caller only
detaches its callback. The refetch always runs to completion, coalesced
callers keep waiting, and even when every caller times out the cache
still gets populated in the background so the next publish fast-paths
instead of paying the RPC round-trip again.

Detaching is safe because doRefresh cannot fail with an unhandled
exception: the eth_call wrappers catch CatchableError and return
Result, and failures surface to callers through fetchOk / an empty
cache exactly as before.

Adds a test cancelling the initiating caller mid-refetch and asserting
the shared future survives and the coalesced caller completes with a
populated cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Move message publish retries out of the kernel on stale RLN proof

On a stale-merkle-proof rejection the kernel and REST layers no longer
republish internally. They schedule a detached merkle proof refresh and
fail fast with a distinguishable, retry-worthy error, leaving the retry
decision to the caller.

- add GroupManager.scheduleMerkleProofRefresh: drops the cached path and
  asyncSpawns the refetch (join-shielded, so a cancelled caller never
  cancels the shared refetch); base is a no-op.
- v3 lightpush (node.lightpushPublish): on an RLN-related 420/504, call
  scheduleMerkleProofRefresh and return immediately, normalized to 504
  OUT_OF_RLN_PROOF with RlnProofRefreshScheduledMsg. The internal
  republish and its bounded timeout are gone.
- send service: lightpushPublishToAny now routes through
  node.lightpushPublish so every NextRoundRetry round regenerates the
  proof against the current cache; the existing OUT_OF_RLN_PROOF ->
  NextRoundRetry mapping is the retry.
- REST lightpush: pass the 504 through unchanged (no internal retry).
- REST relay: attachRlnProofValidateWithRetry -> attachRlnProofAndValidate;
  validate once, on RLN-invalid schedule the refresh and return 503 with
  the marker instead of regenerating and revalidating in-request.
- rename RlnRefreshRetryTimeout -> RlnMerkleProofRefreshTimeout; it now
  bounds only the legacy lightpush refresh, which keeps its one-shot
  internal retry (no wire error taxonomy to delegate to callers).

Not touched: legacy lightpush retry behavior, the proof.nim
attachRLNProof/checkAndGenerateRLNProof split, and the merkle path
refetch/coalescing internals of ensureFreshMerkleProofPath.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tighten RLN proof-refresh docstrings and comments

Condense the verbose comments introduced with the reactive merkle
proof-refresh work. No code changes.

- group_manager_base: base methods carry the contract; the
  OnchainGroupManager overrides keep only their concrete behavior
  instead of repeating the rationale.
- proof.nim, lightpush.nim, constants.nim: trim multi-line docstrings
  and inline comments to their load-bearing facts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the merkle proof path refresh safe against concurrent invalidation

ensureFreshMerkleProofPath returns the fetched path instead of leaving
callers to re-read merkleProofCache. generateProof uses the returned
value, so an invalidate landing during the refresh's updateMemberCache
suspension can no longer empty the cache under an in-flight proof-gen
and trip its empty-cache guard. The cache field is now only a hint for
the next call.

invalidateMerkleProofCache also bumps merkleProofCacheGeneration. The
refresh stamps the generation before each fetchMerkleProofElements and
only caches a result whose generation is unchanged, otherwise it fetches
again, bounded by MerkleProofRefetchMaxAttempts. A rejection-driven
invalidate is therefore never absorbed by a fetch that predates it, so
the path a caller receives always postdates the invalidate.

refreshRoots awaits rootsRefreshInFlightFut via join() at both await
sites, giving the roots refresh the same cancellation shield the proof
path refetch already had: a cancelled validateRoot caller no longer
cancels the refresh its coalesced peers are waiting on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 11:27:37 +02:00
NagyZoltanPeter f28cc7118c Fix FFI peerId pretty print (#4023)
* Add import fix to node_api too as that has a single stringify peerId spot. Thx @fcecin
2026-07-13 14:17:09 +02:00
Fabiana Cecin 53c084dfdb chore: move conf types to api/conf (#4024)
* move MessagingClientConf to api/conf/messaging_conf
* move ReliableChannelManagerConf to new api/conf/channels_conf
* impl modules now import their conf module, not the reverse
* logos_delivery_conf uses channels_conf, not reliable_channel_manager
* cleanup import lists
2026-07-10 09:33:28 -03:00
Fabiana Cecin 9f2a1c89ff Move api config modules to api/conf/ (#4022)
Move api config modules to api/conf/
2026-07-09 18:14:37 -03:00