From a65736b65aae94717026df0e8b04e1bf22dfeaaf Mon Sep 17 00:00:00 2001 From: stubbsta Date: Wed, 8 Jul 2026 09:48:41 +0200 Subject: [PATCH] Bound lightpush RLN refresh-retry with a 5s timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../waku/node/waku_node/lightpush.nim | 56 +++++++++++++------ logos_delivery/waku/rln/constants.nim | 6 ++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/logos_delivery/waku/node/waku_node/lightpush.nim b/logos_delivery/waku/node/waku_node/lightpush.nim index ae502233c..256249f8c 100644 --- a/logos_delivery/waku/node/waku_node/lightpush.nim +++ b/logos_delivery/waku/node/waku_node/lightpush.nim @@ -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() diff --git a/logos_delivery/waku/rln/constants.nim b/logos_delivery/waku/rln/constants.nim index 6ffe51d2b..a2097ffc5 100644 --- a/logos_delivery/waku/rln/constants.nim +++ b/logos_delivery/waku/rln/constants.nim @@ -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