logos-delivery/tests/messaging/test_rln_proof_attach.nim
stubbsta 1b9407cf9b
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>
2026-07-16 14:41:20 +02:00

103 lines
3.4 KiB
Nim

{.used.}
import std/[options, net, osproc]
import chronos, testutils/unittests, results, stew/byteutils
import
logos_delivery/waku/[waku, waku_core, rln],
logos_delivery/waku/node/waku_node,
logos_delivery/waku/node/waku_node/relay,
logos_delivery/waku/api/publish,
logos_delivery/api/conf/messaging_conf,
logos_delivery/waku/factory/waku_conf
import
../testlib/testasync,
../waku_rln_relay/utils_onchain,
../waku_rln_relay/rln/waku_rln_relay_utils
proc testConf(): WakuConf =
var conf = MessagingClientConf()
.toWakuNodeConf(messaging_conf.LogosDeliveryMode.Core).valueOr:
raiseAssert error
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1
conf.rest = false
return conf.toWakuConf().valueOr:
raiseAssert error
proc testMessage(): WakuMessage =
WakuMessage(
payload: "hello".toBytes(),
contentTopic: "/test/1/attach/proto",
timestamp: 1_700_000_000_000_000_000,
)
suite "SendService RLN proof attach":
asyncTest "passes the message through unproven when RLN is not mounted":
## The default (no-RLN) configuration must be unaffected: no proof is
## attached and the message reaches the send processors unchanged.
let waku = (await Waku.new(testConf())).expect("Waku.new")
let msg = testMessage()
let attached = (await waku.attachRlnProof(msg)).expect("attachRlnProof")
check:
attached.proof.len == 0
attached.payload == msg.payload
attached.contentTopic == msg.contentTopic
suite "SendService RLN proof attach - RLN mounted":
var
waku {.threadvar.}: Waku
anvilProc {.threadvar.}: Process
manager {.threadvar.}: OnchainGroupManager
asyncSetup:
anvilProc = runAnvil(stateFile = some(DEFAULT_ANVIL_STATE_PATH))
manager = waitFor setupOnchainGroupManager(deployContracts = false)
waku = (await Waku.new(testConf())).expect("Waku.new")
await waku.node.setRlnValidator(
getWakuRlnConfig(
manager = manager,
userMessageLimit = 20,
index = MembershipIndex(1),
epochSizeSec = 600,
)
)
let credentials = generateCredentials()
(
waitFor cast[OnchainGroupManager](waku.node.rln.groupManager).register(
credentials, UserMessageLimit(20)
)
).isOkOr:
assert false, "failed to register RLN credentials: " & error
asyncTeardown:
## The RLN proof-generator provider is registered on the global broker
## context; without stopping RLN it leaks into the next test's setup.
try:
await waku.node.rln.stop()
except Exception:
assert false, "failed to stop RLN: " & getCurrentExceptionMsg()
stopAnvil(anvilProc)
asyncTest "attaches a proof":
let attached = (await waku.attachRlnProof(testMessage())).expect("attachRlnProof")
check attached.proof.len > 0
asyncTest "is idempotent: a message that already carries a proof is untouched":
## Pins the retry contract: the send service re-attaches on every round, so
## re-attaching must neither draw a fresh nonce nor change the bytes —
## otherwise a retried task would resend under a new nullifier.
let first = (await waku.attachRlnProof(testMessage())).expect("first attach")
let second = (await waku.attachRlnProof(first)).expect("second attach")
check:
first.proof.len > 0
second.proof == first.proof