feat(chat): membership status watcher + bump nwaku for 2-phase gifter

src/chat/delivery/waku_client.nim:
- After the gifter-client returns its optimistic leaf_index, spawn a
  background watcher that polls the gifter's new membership-status
  RPC (short-lived libp2p streams on /logos/rln/membership/status/
  1.0.0) until the on-chain membership PDA materialises for our id.
  If the authoritative leaf differs from the optimistic snapshot, the
  watcher rewrites lezGm.membershipIndex. The OnchainLEZGroupManager's
  poll loop picks up the corrected proof on its next iteration;
  spam_protection's self-verify rejects bad proofs until then.

vendor/nwaku bump pulls in:
- New /logos/rln/membership/status/1.0.0 codec + handlers on the
  gifter server.
- MembershipStatusRequest/Response RPC messages.
- WakuRlnGifterClient.queryMembershipStatus.
- Mix-node gifter-client watcher with the same pattern.
This commit is contained in:
Arseniy Klempner 2026-05-21 11:15:17 -06:00
parent ef5a98317d
commit 295b8d7933
No known key found for this signature in database
GPG Key ID: 51653F18863BD24B
2 changed files with 47 additions and 1 deletions

View File

@ -451,6 +451,52 @@ proc start*(client: WakuClient) {.async.} =
leafIndex = success.leafIndex,
configAccount = configAccountId
# Background watcher: gifter returns a PRE-SUBMIT leaf_index snapshot,
# which can be wrong if a concurrent register tx claimed the slot
# first. Poll the gifter over a SEPARATE short-lived libp2p stream
# (membership status codec) until the on-chain membership PDA
# materialises for our id; if the authoritative leaf differs,
# rewrite lezGm.membershipIndex so subsequent proofs are valid. The
# pre-publish self-verify rejects bad proofs in the meantime.
let optimisticLeafChat = success.leafIndex
let watcherClient = gifterClient
let watcherPeer = gifterPeer
let watcherIdCommit = idCommitmentBytes
asyncSpawn (proc(): Future[void] {.async.} =
const pollEveryMs = 30_000
const deadlineMs = 1_800_000
let deadline = Moment.now() + chronos.milliseconds(deadlineMs)
while Moment.now() < deadline:
try:
await sleepAsync(chronos.milliseconds(pollEveryMs))
except CancelledError:
return
let qr =
try:
await watcherClient.queryMembershipStatus(
watcherPeer, configAccountId, watcherIdCommit)
except CancelledError:
return
except CatchableError as e:
Result[rln_gifter_protocol.MembershipStatusResponse, string].err(
"queryMembershipStatus raised: " & e.msg)
if qr.isErr: continue
let resp = qr.get()
if resp.errorMessage.isSome: continue
if not resp.registered: continue
if resp.leafIndex.isSome:
let authLeaf = resp.leafIndex.get()
if authLeaf != optimisticLeafChat:
info "Chat-client membership leaf corrected from optimistic",
optimistic = optimisticLeafChat, authoritative = authLeaf
lezGm.membershipIndex =
some(onchain_group_manager.MembershipIndex(authLeaf))
mix_lez_client.setRlnConfig(configAccountId, authLeaf.int)
return
warn "Chat-client membership confirmation timed out",
optimisticLeaf = optimisticLeafChat
)()
asyncSpawn taskKeepAlive(client)
if client.cfg.mixEnabled and not client.node.wakuMix.isNil:

2
vendor/nwaku vendored

@ -1 +1 @@
Subproject commit 33d9d89e00be774d33ceafd3d59755d5a1089bb4
Subproject commit ce6f5c2d5e184b7c94a65068d4e5f29936259020