mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-20 11:40:02 +00:00
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>
103 lines
3.4 KiB
Nim
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
|