Skip to content

Commit 72ced79

Browse files
grunchclaude
andcommitted
fix(pow): bound the post-timeout PoW probe to avoid doubling latency
fetch_required_pow uses FETCH_EVENTS_TIMEOUT (15s) internally, so a slow/unreachable relay would let wait_for_dm wait 15s for the reply plus another 15s for the probe before producing an error. Wrap the probe in a short tokio::time::timeout (3s) and fall through to WaitForDmTimeout if it doesn't return in time — Ok(Some(required)) is the only case that escalates to PowRequirementUnmet. Addresses review feedback on #173. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8680419 commit 72ced79

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

docs/pow_error_handling.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ let event = match waited {
130130
Err(_elapsed) => {
131131
// Before declaring this a generic timeout, check whether the daemon
132132
// advertises a PoW requirement we didn't meet — that's the real
133-
// cause "deadline has elapsed" was hiding.
134-
if let Some(required) = fetch_required_pow(ctx).await {
133+
// cause "deadline has elapsed" was hiding. Bounded by
134+
// POW_PROBE_TIMEOUT so a slow/unreachable relay can't double the
135+
// user-visible wait; if the probe doesn't return in time we fall
136+
// through to the generic timeout error instead of hanging.
137+
let probe = tokio::time::timeout(POW_PROBE_TIMEOUT, fetch_required_pow(ctx)).await;
138+
if let Ok(Some(required)) = probe {
135139
let configured = parse_pow_env().unwrap_or(0);
136140
if required > configured {
137141
return Err(PowRequirementUnmet { required, configured }.into());
@@ -142,6 +146,10 @@ let event = match waited {
142146
};
143147
```
144148

149+
`POW_PROBE_TIMEOUT` is a small constant (currently 3 s) — well below
150+
`FETCH_EVENTS_TIMEOUT` (15 s). Worst-case user-visible wait stays at one
151+
`FETCH_EVENTS_TIMEOUT` plus the probe budget instead of doubling.
152+
145153
Add an `&Context` parameter? Look at the signature today —
146154
`wait_for_dm(ctx, order_trade_keys, sent_message)``ctx` is already
147155
passed. We just need to grant the helper access to `ctx.client` and

src/util/messaging.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ pub async fn send_plain_text_dm(
256256
.await
257257
}
258258

259+
/// Upper bound on the post-timeout PoW probe inside [`wait_for_dm`]. Kept
260+
/// well below `FETCH_EVENTS_TIMEOUT` so a slow/unreachable relay can't
261+
/// double the user-visible wait — if the kind-38385 info event isn't back
262+
/// within this budget we fall through to [`WaitForDmTimeout`] instead.
263+
const POW_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
264+
259265
/// Distinguishable error returned by [`wait_for_dm`] when no reply arrives
260266
/// within [`FETCH_EVENTS_TIMEOUT`].
261267
///
@@ -347,7 +353,15 @@ where
347353
// no reply ever comes). Before declaring this a generic timeout,
348354
// ask the daemon's kind-38385 info event whether that's actually
349355
// the cause we're hiding behind "no reply".
350-
if let Some(required) = super::events::fetch_required_pow(ctx).await {
356+
//
357+
// The probe is bounded by `POW_PROBE_TIMEOUT` instead of the full
358+
// `FETCH_EVENTS_TIMEOUT` so a slow/unreachable relay can't double
359+
// the user-visible wait. If the probe doesn't return in time, fall
360+
// through to the generic timeout error rather than hanging.
361+
let probe =
362+
tokio::time::timeout(POW_PROBE_TIMEOUT, super::events::fetch_required_pow(ctx))
363+
.await;
364+
if let Ok(Some(required)) = probe {
351365
let configured = parse_pow_env().unwrap_or(0);
352366
if required > configured {
353367
return Err(PowRequirementUnmet {

0 commit comments

Comments
 (0)