* 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>
* 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>
* Rename WakuMode to LogosDeliveryMode, add Fleet mode
* KernelConf (which starts only kernel) is now a distinct WakuNodeConf (starts messaging, for tests)
* Add LogosDelivery.new(kernelConf) (kernel-only)
* Add LogosDelivery.new(kernelConf, messagingOverrides, channelsOverrides) (full stack)
* LogosDeliveryConf.messagingOverrides/channelsOverrides are Option and mount each layer if present
* Add kernel-only LogosDeliveryConf.init(kernelConf)
* Parse {"mode": "fleet", "kernelConf": {...}} in parseLogosDeliveryConf
* Generalize parseOverrides to parse over caller-supplied defaults
* Add requireMessaging/requireChannels FFI guards and apply them
* Make start/stop skip layers that were not mounted (no config)
* Rename toKernelConf to toWakuNodeConf
* Add tests to test_conf.nim
* Misc additions/refactors
* remove --mode from the CLI
* move WakuMode to the messaging layer
* expose store backend (db url, max connections) and a remote store node on the messaging surface
* wakunode2 with no flags now runs as a full service node (store still opt-in)
* add rateLimitMessagesPerEpoch
* channel rate-limiting auto-enables if epochPeriodSec or messagesPerEpoch is set
* fix JSON conf parser to be generic (works over all config types)
* messaging config = mode + preset + messagingOverrides + channelsOverrides
* add full messaging plus selective kernel config options to MessagingClientConf
* mode (Core/Edge) expands to kernel protocol flags in the messaging layer
* create_node parses the messaging config, drops the flat WakuNodeConf JSON entrypoint
* wire channelsOverrides (segmentation/SDS/rate-limit) into channel creation
* fix liblogosdelivery.h comments and README for the new config shape
* messaging conf tests: switch names, reject-unknown, set-twice, field->kernel
* add kernel log-level, log-format, nodekey to the messaging surface
* Port 0 (ephemeral) default for messaging entry points
* KernelConf alias for WakuNodeConf
* rewrite the FFI examples to the new config shape
* C/C++ examples use preset status.prod
* drop operator-only confs from the examples
* remove duplicate tests & misc test fixes
* Delete p2pReliability from Kernel (Waku) resolver and config (keep preset definition)
* Delete NodeConfig API (deprecation completed by p2pReliability removal from kernel)
* Rename test_messaging_conf.nim to test_conf.nim (tests Logos Delivery config in general)
* Rename messaging_conf_json.nim to logos_delivery_conf_json.nim
* Add logos_delivery_conf.nim (defines LogosDeliveryConf aggregate)
* misc docs/comments cleanups
Add a `status.prod` network preset usable from the Messaging API and CLI
(`--preset=status.prod`). It targets the live status.prod network that
Status runs on:
- cluster-id 16
- RLN disabled
- static sharding (fleet shards 1, 32, 64, 128, 256; Status uses 32 & 64)
- max message size 1024KiB
- bootstrap via the status.prod DNS-discovery enrtree and the three fleet boot nodes
Config sourced from https://fleets.waku.org/ and each host's /config.toml.
Closes#3906
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* squash original commits and rebase to master: removed sendHandler and replaced by MessagingSend BrokerRequest
* Needed to adapt to new messaging source structure to avoid cyclic imports
* Relocate MessagingSend request broker and eliminate api/requests
* fix ci fails
* remove RLN proof generation from lightpush server to client
* set timestamp before generating lightpush msg proof
* Add explicit error message for checkAndGenerateRLNProof and oneline for ensureTimestampSet
* Change ensureTimestampSet from func to proc
* Fold portsShift into each port in WakuConfBuilder.build()
* Drop portsShift field from WakuConf
* Drop portsShift param from networkConfiguration, discv5 setup, rest/metrics start
* Keep Port(0) auto-assign sentinel (also fixes port-0 + shift)
* Add regression test: announced port == bound port under portsShift
* Fix lint
* Reshape per-layer API into api/ folders and thin the FFI over them
Each layer now separates its constructible core from its public surface:
- core module (waku.nim / messaging_client.nim /
reliable_channel_manager.nim): the type plus new/start/stop and the
private construction helpers.
- api/ folder: one module per differentiated set of operations
(waku: topics/relay/filter/lightpush/store/peer_manager/discovery/
debug/health) plus an events surface.
The waku api is reshaped to be the complete operation surface the C
bindings need, so the library no longer reaches into node internals:
relayPublish returns the message hash, relaySubscribe takes an optional
handler, filter/lightpush auto-select the service peer, connectedPeersInfo
returns structured data, pingPeer honours the timeout, plus
relayNumPeersInMesh / relayNumConnectedPeers / isOnline. library/ is now a
thin C-ABI shim: each {.ffi.} proc only marshals cstring/JSON/callbacks and
delegates to ctx.myLib[].waku.<op> (or messagingClient.<op>).
app_callbacks re-exports the modules defining its handler types, which the
included FFI files previously relied on by leakage.
Events move next to the surface that owns them, with each dependency kept
pointing the right way:
- waku/events/ relocated under waku/api/events/.
- channel events live in channels/api/events.nim.
- the four messaging-level message events move to messaging/api/events;
MessageSeenEvent stays in waku because it is emitted by waku core, so
moving it would make waku depend on the messaging layer.
- delivery_events renamed to filter_subscribe_events to match the
OnFilterSubscribe/Unsubscribe events it actually declares.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add reliable-channel FFI ops + events (nim-ffi v0.1.3)
Expose the reliable-channel layer through the v0.1.3 FFI:
- channel_create / channel_send / channel_close call the
ReliableChannelManager api (createReliableChannel / send / closeChannel),
marshalling channel id + base64 payload + ephemeral by hand
- channel message received / sent / errored are surfaced by listening to the
channel-layer broker events in start_node and forwarding them through
callEventCallback (received payload base64-encoded), dropped in stop_node
Stays on nim-ffi v0.1.3 (no typed/CBOR rewrite).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Expose reliable-channel ops in the stable C header (#3851)
The library already ships as a single .so with a tiered header surface
(liblogosdelivery.h = stable Messaging/Reliable-Channels, liblogosdelivery_kernel.h
= advanced Kernel). Per that tiering, the reliable-channel ops belong on the
stable surface, so declare channel_create / channel_send / channel_close in
liblogosdelivery.h and document the channel lifecycle events delivered through
the event callback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Graft PR#3975 interface layer onto decomposed foundation (events deduped)
Add IKernel/IMessagingClient/IReliableChannelManager/ILogosDelivery interface
classes under logos_delivery/api/. The EventBroker types PR#3975 hoisted into
these files already exist in PR#3989's decomposed */api/events/ modules, so the
interface files re-export those modules instead of redefining the types
(avoids 8 duplicate EventBroker definitions). api/types.nim kept at the
foundation version (ChannelId stays in channels/types.nim, which the decomposed
modules import).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Wire impl classes to interfaces (inherit; relocate SendHandler)
- Waku : IKernel, MessagingClient : IMessagingClient,
ReliableChannelManager : IReliableChannelManager.
- The operation procs already live in PR#3989's decomposed */api/ modules and
stay as plain procs (nothing dispatches through the interface types, so no
method-ization is needed).
- SendHandler now lives in reliable_channel_manager_api.nim (its PR#3975 home);
removed the duplicate from reliable_channel.nim, which re-exports the
interface module so channels/api/{channel_lifecycle,send} still see it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Wire LogosDelivery to ILogosDelivery orchestrator interface
LogosDelivery : ILogosDelivery; start/stop/isOnline become method overrides.
Peripheral PR#3975 edits (lightpush/store clients, self_req_handlers,
statistics) are import-reorg artifacts of deleting waku/utils/requests.nim,
which the decomposed structure keeps -- so they are intentionally not ported.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Dedup EventConnectionStatusChange (re-export from health_events)
9th duplicate EventBroker type: defined in both logos_delivery_api.nim and the
decomposed waku/api/events/health_events.nim. The interface file now re-exports
it. liblogosdelivery builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Move events back into interface-class source files (restore #3975 placement)
Reverses the earlier dedup-by-re-export: event TYPE definitions now live in the
interface classes, and the emptied decomposed event files are removed.
- MessageSeenEvent -> logos_delivery/api/kernel_api.nim
- Message{Sent,Error,Propagated,Received}Event -> api/messaging_client_api.nim
- ChannelMessage{Received,Sent,Error}Event -> api/reliable_channel_manager_api.nim
- EventConnectionStatusChange -> api/logos_delivery_api.nim
Deleted (became empty after the move):
- logos_delivery/waku/api/events/message_events.nim
- logos_delivery/messaging/api/events.nim
- logos_delivery/channels/api/events.nim
health_events.nim keeps its two remaining events (content/shard topic health).
Rewiring: each layer re-exports its interface module (waku->kernel_api,
messaging_client->messaging_client_api, reliable_channel->reliable_channel_manager_api,
which also re-exports messaging_client_api). Deep emitters/listeners
(subscription_manager, waku_node, waku_node/relay, node_health_monitor,
recv_service, send_service) import the owning interface module directly.
kernel_api stays below node level (types/topics/message/store-common) so the
node->kernel_api imports are acyclic. liblogosdelivery builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* nph formatting
---------
Co-authored-by: Ivan FB <ivansete@status.im>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Add rln validator to mountRlnRelay
* remove requirement for relay when mounting RLN
* Rename mountRlnRelay to setRlnValidator
* Clean up setRlnValidator: update log wording, drop unused let bindings
* Fix botched setRlnValidator callsites from rename
* Drop obsolete test: setRlnValidator no longer requires relay
* Remove unused code in test utils
* remove unused import for rln
* move rln specific procs and types
* Renaming and restructuring RLN module
* Rename waku_rln_relay to waku_rln
* rename rln-relay types to rln
* Fix linting
* update logScope topics to match new rln module name
* Rename waku_rln module to rln
* Rename WakuRln type to Rln, rename FFI raw type to RlnRaw to avoid collision
* Apply nph formatting
* Rename local rlnRelay to rln in mountRlnRelay
* Move waku.nim from waku/factory to under waku/
* remove unused
* Realize Kernel API in scope of Waku class
* Refactor waku/api into messaging_client, waku/api/types and api_conf into logos_delivery/api
* Make liblogosdelivery and wakunode2 compile, remove waku/api.nim as it was just a import orchestrator
* make test compile and run
* Reconcile master's new send tests to LogosDelivery API after rebase
master commits #3965/#3669-followup added two test cases (Edge lightpush
delivery #3847, store-validation timeout) written against the removed
waku/api.nim createNode helper. Rewrite them to the LogosDelivery shape:
createNode -> LogosDelivery.new, node.node -> node.waku.node,
node.brokerCtx -> node.waku.brokerCtx, node.send -> node.messagingClient.send,
and drop the now-implicit mountMessagingClient calls (LogosDelivery.new
mounts the client internally).
* ConnectionStatus transition to connected now trigger store query
* Query period computed over period in which the node was disconnected
* remove periodic 5min store query
* add connection status edge-triggered test case
* refactor RecvService test suite
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
* make generateProof async and add ensureFreshMerkleProofPath
* Use Wakumessage.new()
* Add trigger for client side only merkleproofcache updates
* full decoupling of updateRoots and merkleproofcache update
* Add tests for on demand merkle path updates
* debugging WIP
* fix for getTransactionReceipt endless loop
* clean up group manager logs
* Remove unused code from rebase mistake
* Improve Anvil proc for RLN testing
* fixing from rebase
* Fix message ref in tests and clean up logs in utils_onchain
* Clean up more logs
* Change root update log to trace
* Increase approved token amount for RLN registration testing
* Reduce comments in utils_onchain
* simplify benchmarks test result output
* Add test names
* remove duplicated tests for test_rln_groupmanager
* Trim group_manager tests
* Remove long test for local window of roots and improve amvil test performance
* Optimise tests for group_manager
* additive quic transport, off by default (--quic-support)
* add QuicConf + QuicConfBuilder, --quic-support / --quic-port flags
* net_config announces quic-v1 host/ext/dns4 addrs, adds quic-v1 to enr multiaddrs
* newWakuSwitch mounts quic transport when a quic addr is set
* toRemotePeerInfo: quic from enr multiaddrs ext, sorted quic-first
* BoundPorts.quic, read back the bound quic port at start (handles --quic-port=0)
* skip auto quic addr when operator supplies one via --ext-multiaddr
* tests: nodes dual-stack by default, tcp-only where single transport asserted
* tests: drop hardcoded ephemeral ports (port 0) to fix quic-churn bind flakes
* use setupNat to discover NAT-mapped UDP port when QUIC enabled
* make validateRoots async
* add on-demand refreshRoots functionality
* Move max rootsrefresh time to constants
* make generateProof async and add ensureFreshMerkleProofPath
* Update to match code format and linting
* Use Wakumessage.new()
* Add trigger for client side only merkleproofcache updates
* full decoupling of updateRoots and merkleproofcache update
* Fix isNil check format
* Move moment check to top of roots and merkle path update procs
* Update PathCheckMinInterval
* Add tests for on demand merkle path updates
* Replace appendRLNProof and use message.toRLNSignal
* Fix linting
* Remove commented code
* Remove more old commented code
* Fix formatting and simplifications
* rename NetworkConf -> NetworkPresetConf and related procs/vars
* Rewrite applyNetworkPresetConf to apply user-set fields over preset fields
* New dedicated parser for configJson
* Fix tests to use actual extract JSON nodeconf parser
* Change all confbuilder defaults from literal values to DefaultXXX consts
* Change int/bool WakuNodeConf fields to Option to get user intent w/o sentinels
* Make Option CLI default-value help mention defaults now owned by confbuilder
* Document CLI defaults that differ from confbuilder defaults
* Fix agent-string builder default deviating from CLI default
* Add WakuConfBuilder.enforceSecurityConstraints()
* Fail on RLN user preset overrides instead of drop-and-continue
* Add regression tests for initial set of conf constraints
* fix kademliaDiscoveryConfBuilder.build() enable/disable kad logic
* Misc refactors, fixes
* Add tests
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
* Only add new roots, not all received
* Fix error in removing recent roots not checking AcceptableWindowSize
* fix merging
* more merging fixes
* merge fixes
* add test for updated merkle roots window
* add pr re-add gauge for proof-generation-duration-seconds
* Decrease AcceptableRootWindowSize for testing
* debug spam log
* linting
* start trackRootChanges call loop immediately
* Fix 5s delay trackRootChanges
* set rpcDelay for root tracking to 10s
* add default params to sendEthCallWithParams
* improve recents roots retrieval and logs
* Use updateRecentRoots to track root changes
* simplify updateRecentRoots
* set root polling to 15s
* set rpc poll delay to 30s
* set acceptablerootwindowsize and root poll delay
* Improve test 'should fetch history correctly' for root cache
* Make root cache handling more efficient
* add contract root cache size as constant and function use fix
* updateRecentRoots comments update
* Update group_manager and tests
* fix linting
* persistency: follow nim-sds 0.3.0 snapshot persistence contract
nim-sds 0.3.0 replaced the ~14 fine-grained per-row Persistence callbacks
with a 5-proc snapshot model (saveChannelMeta / updateHistory / loadChannel
/ dropChannel / setRetrievalHint), all returning Future[Result[...]].
Rewrite waku/persistency/sds_persistency.nim accordingly:
- ChannelMeta is stored as one blob per channel; the message log as
append/evict rows. Categories collapse from 7 to 2 (sds.meta, sds.log).
- Blob transform uses nim-sds' own codecs: snapshot_codec (schema-versioned
protobuf) for ChannelMeta, the SDS wire codec for SdsMessage log rows. The
generic payload_codec/BlobCodec path is retired (removed payload_codec.nim
and test_blob_codec.nim).
- setRetrievalHint is a deliberate no-op: persisted hints are never read back
(loadChannel/ChannelMeta carry none; hints are supplied live via the
onRetrievalHint provider). The closure stays because the field is required.
- Fix the module import spelling (srcDir="sds" => bare module paths), which
the previous adapter got wrong and never compiled against the locked deps.
Add tests/persistency/test_sds_persistency.nim (round-trip, empty-load,
evict, drop) replacing test_blob_codec in test_all. Full persistency suite
passes 74/74 under both refc and ORC.
* Bump to latest nim-sds and nim-brokers 3.1.1
* Update with latest nim-sds changes - removal of setRetrievalHints - not needed
* persistency: per-job SQLite-backed storage layer (singleton, brokered)
Adds a backend-neutral CRUD library at waku/persistency/, plus the
nim-brokers dependency swap that enables it.
Architecture (ports-and-adapters):
* Persistency: process-wide singleton, one root directory.
* Job: one tenant, one DB file, one worker thread, one BrokerContext.
* Backend: SQLite via waku/common/databases/db_sqlite. Uniform schema
kv(category BLOB, key BLOB, payload BLOB) PRIMARY KEY (category, key)
WITHOUT ROWID, WAL mode.
* Writes are fire-and-forget via EventBroker(mt) PersistEvent.
* Reads are async via five RequestBroker(mt) shapes (KvGet, KvExists,
KvScan, KvCount, KvDelete). Reads return Result[T, PersistencyError].
* One storage thread per job; tenants isolated by BrokerContext.
Public surface (waku/persistency/persistency.nim):
Persistency.instance(rootDir) / Persistency.instance() / Persistency.reset()
p.openJob(id) / p.closeJob(id) / p.dropJob(id) / p.close()
p.job(id) / p[id] / p.hasJob(id)
Writes (Job form & string-id form, fire-and-forget):
persist / persistPut / persistDelete / persistEncoded
Reads (Job form & string-id form, async Result):
get / exists / scan / scanPrefix / count / deleteAcked
Key & payload encoding (keys.nim, payload.nim):
* encodePart family + variadic key(...) / payload(...) macros +
single-value toKey / toPayload.
* Primitives: string and openArray[byte] are 2-byte BE length + bytes;
int{8..64} are sign-flipped 8-byte BE; uint{16..64} are 8-byte BE;
bool/byte/char are 1 byte; enums are int64(ord(v)).
* Generic encodePart[T: tuple | object] recurses through fields() so
any composite Nim type is encodable without ceremony.
* Stable across Nim/C compiler upgrades: no sizeof, no memcpy, no
cast on pointers, no host-endianness dependency.
* `rawKey(bytes)` + `persistPut(..., openArray[byte])` let callers
bypass the built-in encoder with their own format (CBOR, protobuf...).
Lifecycle:
* Persistency.new is private; Persistency.instance is the only public
constructor. Same rootDir is idempotent; conflicting rootDir is
peInvalidArgument. Persistency.reset for test/restart paths.
* openJob opens-or-creates the per-job SQLite file; an existing file
is reused with its data preserved.
* Teardown integration: Persistency.instance registers a Teardown
MultiRequestBroker provider that closes all jobs and clears the
singleton slot when Waku.stop() issues Teardown.request.
Internal layering:
types.nim pure value types (Key, KeyRange, KvRow, TxOp,
PersistencyError)
keys.nim encodePart primitives + key(...) macro
payload.nim toPayload + payload(...) macro
schema.nim CREATE TABLE + connection pragmas + user_version
backend_sqlite.nim KvBackend, applyOps (single source of write SQL),
getOne/existsOne/deleteOne, scanRange (asc/desc,
half-open ranges, open-ended stop), countRange
backend_comm.nim EventBroker(mt) PersistEvent + 5 RequestBroker(mt)
declarations; encodeErr/decodeErr boundary helpers
backend_thread.nim startStorageThread / stopStorageThread (shared
allocShared0 arg, cstring dbPath, atomic
ready/shutdown flags); per-thread provider
registration
persistency.nim Persistency + Job types, singleton state, public
facade
../requests/lifecycle_requests.nim
Teardown MultiRequestBroker
Tests (69 cases, all passing):
test_keys.nim sort-order invariants (length-prefix strings,
sign-flipped ints, composite tuples, prefix
range)
test_backend.nim round-trip / replace / delete-return-value /
batched atomicity / asc-desc-half-open-open-
ended scans / category isolation / batch
txDelete
test_lifecycle.nim open-or-create rootDir / non-dir collision /
reopen across sessions / idempotent openJob /
two-tenant parallel isolation / closeJob joins
worker / dropJob removes file / acked delete
test_facade.nim put-then-get / atomic batch / scanPrefix
asc/desc / deleteAcked hit-miss /
fire-and-forget delete / two-tenant facade
isolation
test_encoding.nim tuple/named-tuple/object keys, embedded Key,
enum encoding, field-major composite sort,
payload struct encoding, end-to-end struct
round-trip through SQLite
test_string_lookup.nim peJobNotFound semantics / hasJob / subscript /
persistPut+get via id / reads short-circuit /
writes drop+warn / persistEncoded via id /
scan parity Job-ref vs id
test_singleton.nim idempotent same-rootDir / different-rootDir
rejection / no-arg instance lifecycle / reset
retargets / reset idempotence / Teardown.request
end-to-end
Prerequisite delivered in the same series: replace the in-tree broker
implementation with the external nim-brokers package; update all
broker call-sites (waku_filter_v2, waku_relay, waku_rln_relay,
delivery_service, peer_manager, requests/*, factory/*, api tests, etc.)
to the new package API; chat2 made to compile again.
Note: SDS adapter (Phase 5 of the design) is deferred -- nim-sds is
still developed side-by-side and the persistency layer is intentionally
SDS-agnostic.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* persistency: pin nim-brokers by URL+commit (workaround for stale registry)
The bare `brokers >= 2.0.1` form cannot resolve on machines where the
local nimble SAT solver enumerates only the registry-recorded 0.1.0 for
brokers. The nim-lang/packages entry for `brokers` carries no per-tag
metadata (only the URL), so until that registry entry is refreshed the
SAT solver clamps the available-versions list to 0.1.0 and rejects the
>= 2.0.1 constraint -- even though pkgs2 and pkgcache both have v2.0.1
cloned locally.
Pinning by URL+commit bypasses the registry path entirely. Inline
comment in waku.nimble documents the situation and the path back to
the bare form once nim-lang/packages is updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* persistency: nph format pass
Run `nph` on all 57 Nim files touched by this PR. Pure formatting:
17 files re-styled, no semantic change. Suite still 69/69.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Fix build, add local-storage-path config, lazy init of Persistency from Waku start
* fix: fix nix deps
* fixes for nix build, regenerate deps
* reverting accidental dependency changes
* Fixing deps
* Apply suggestions from code review
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
* persistency tests: migrate to suite / asyncTest / await
Match the in-tree test convention (procSuite -> suite, sync test +
waitFor -> asyncTest + await):
- procSuite "X": -> suite "X":
- For tests doing async work: test -> asyncTest, waitFor -> await.
- Poll helpers (proc waitFor(t: Job, ...) in test_lifecycle.nim,
proc waitUntilExists(...) in test_facade.nim and
test_string_lookup.nim) -> Future[bool] {.async.}, internal
`waitFor X` -> `await X`, internal `sleep(N)` ->
`await sleepAsync(chronos.milliseconds(N))`.
- Renamed test_lifecycle.nim's helper proc from `waitFor(t: Job, ...)`
-> `pollExists(t: Job, ...)`; the previous name shadowed
chronos.waitFor in the chronos macro expansion.
- `chronos.milliseconds(N)` explicitly qualified because `std/times`
also exports `milliseconds` (returning TimeInterval, not Duration).
- `check await x` -> `let okN = await x; check okN` to dodge chronos's
"yield in expr not lowered" with await-as-macro-argument.
- `(await x).foo()` -> `let awN = await x; ... awN.foo() ...` for the
same reason.
waku/persistency/persistency.nim: nph also pulled the proc signatures
across multiple lines; restored explicit `Future[void] {.async.}`
return types after the colon (an intermediate nph pass had elided them).
Suite: 71 / 71 OK against the new async write surface.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* use idiomatic valueOr instead of ifs
* Reworked persistency shutdown, remove not necessary teardown mechanism
* Use const for DefaultStoragePath
* format to follow coding guidelines - no use of result and explicit returns - no functional change
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>