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>
This commit is contained in:
stubbsta 2026-07-08 09:48:41 +02:00
parent 8b88a63d1b
commit a65736b65a
No known key found for this signature in database
2 changed files with 45 additions and 17 deletions

View File

@ -151,17 +151,27 @@ proc legacyLightpushPublish*(
info "legacy lightpush send rejected as RLN-invalid; " &
"refreshing merkle proof and retrying once"
let (retryMsg, _) = (
await checkAndGenerateRLNProof(
rln,
msgWithProof,
forceMerkleProofRefresh = true,
reuseMessageId = drawnMessageId,
)
).valueOr:
return err("failed call checkAndGenerateRLNProof from lightpush retry: " & error)
return await internalPublish(node, pubsubForPublish, retryMsg, peer)
proc runRetry(): Future[legacy_lightpush_protocol.WakuLightPushResult[string]] {.
async, gcsafe
.} =
let (retryMsg, _) = (
await checkAndGenerateRLNProof(
rln,
msgWithProof,
forceMerkleProofRefresh = true,
reuseMessageId = drawnMessageId,
)
).valueOr:
return
err("failed call checkAndGenerateRLNProof from lightpush retry: " & error)
return await internalPublish(node, pubsubForPublish, retryMsg, peer)
let retryFut = runRetry()
if not (await retryFut.withTimeout(RlnRefreshRetryTimeout)):
warn "legacy lightpush RLN-refresh retry timed out; returning original error"
return firstResult
return retryFut.read()
except CatchableError:
return err(getCurrentExceptionMsg())
@ -347,11 +357,23 @@ proc lightpushPublish*(
info "lightpush send rejected; refreshing merkle proof and retrying once",
statusCode = $firstResult.error.code
let (retryMsg, _) = (
await checkAndGenerateRLNProof(
rln, msgWithProof, forceMerkleProofRefresh = true, reuseMessageId = drawnMessageId
)
).valueOr:
return lighpushErrorResult(LightPushErrorCode.OUT_OF_RLN_PROOF, error)
return await lightpushPublishHandler(node, pubsubForPublish, retryMsg, toPeer, mixify)
proc runRetry(): Future[lightpush_protocol.WakuLightPushResult] {.async, gcsafe.} =
let (retryMsg, _) = (
await checkAndGenerateRLNProof(
rln,
msgWithProof,
forceMerkleProofRefresh = true,
reuseMessageId = drawnMessageId,
)
).valueOr:
return lighpushErrorResult(LightPushErrorCode.OUT_OF_RLN_PROOF, error)
return
await lightpushPublishHandler(node, pubsubForPublish, retryMsg, toPeer, mixify)
let retryFut = runRetry()
if not (await retryFut.withTimeout(RlnRefreshRetryTimeout)):
warn "lightpush RLN-refresh retry timed out; returning original error",
statusCode = $firstResult.error.code
return firstResult
return retryFut.read()

View File

@ -20,6 +20,12 @@ const RlnCredentialsFilename* = "rlnCredentials.txt"
# RLN Validator message rejection Error string, is used to trigger proof refresh and publish retry in the lightpush client
const RlnValidatorErrorMsg* = "RLN validation failed"
# Upper bound on the reactive refresh + retry after a stale-RLN rejection.
# The retry synchronously fetches a fresh merkle path from the RLN contract
# (eth_call round-trip) and republishes; without a bound a hanging RPC endpoint
# could stall the caller indefinitely. Matches the REST `futTimeout`.
const RlnRefreshRetryTimeout* = 5.seconds
# inputs of the membership contract constructor
# TODO may be able to make these constants private and put them inside the waku_rln_utils
const