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>
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>