Skip to content

fix(pow): surface explicit PoW rejection instead of generic timeout#173

Merged
grunch merged 3 commits into
mainfrom
fix/pow-rejection-error
May 28, 2026
Merged

fix(pow): surface explicit PoW rejection instead of generic timeout#173
grunch merged 3 commits into
mainfrom
fix/pow-rejection-error

Conversation

@grunch
Copy link
Copy Markdown
Member

@grunch grunch commented May 28, 2026

Summary

  • When mostrod requires NIP-13 PoW and the CLI sends an event without satisfying it, the daemon silently drops the event (logs Not POW verified event! on its side) and never replies. The CLI surfaced this as a generic timeout (deadline has elapsed / Timeout waiting for DM or gift wrap event), which led users to suspect network/relay/daemon issues instead of the real PoW cause.
  • After timing out, wait_for_dm now consults the daemon's kind-38385 info event (pow tag) and, if the required difficulty exceeds the configured POW, returns the new PowRequirementUnmet { required, configured } error with an actionable message pointing at --pow / POW=.
  • Genuine timeouts keep returning WaitForDmTimeout, so the add-bond-invoice happy-path branch (which only matches WaitForDmTimeout) is unaffected; a PoW failure on that command propagates instead of being misreported as a successful submission.

Design notes and alternatives considered are in docs/pow_error_handling.md.

Closes #172

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test (10 unit tests + integration tests, all green)
  • New unit tests:
    • pow_requirement_unmet_display_mentions_required_and_configured
    • pow_requirement_unmet_is_downcastable_through_anyhow
    • read_info_tag_finds_pow_value
    • read_info_tag_returns_none_when_missing
    • pow_tag_parses_as_u8
  • Manual E2E against a PoW-enabled local mostrod:
    • POW=0 mostro-cli neworder …PowRequirementUnmet error (not "deadline has elapsed")
    • POW=<required> mostro-cli neworder … → normal flow

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • The CLI now detects when message timeouts are caused by Proof-of-Work requirement mismatches and provides actionable error messaging with guidance on configuration.
  • Documentation

    • Added specification for Proof-of-Work requirement error handling and validation in the Mostro protocol.

Review Change Stack

When mostrod requires NIP-13 PoW and the client sends an event without
satisfying it, the daemon silently drops the event (logs "Not POW
verified event!" on its side) and never replies. The CLI was hiding
that behind a generic timeout message ("deadline has elapsed" /
"Timeout waiting for DM or gift wrap event"), which can mislead users
into suspecting network failures, relay issues, or daemon downtime.

After timing out, wait_for_dm now consults the daemon's kind-38385
info event ("pow" tag) and, if the required difficulty exceeds the
configured POW, returns the new PowRequirementUnmet error with an
actionable message pointing at --pow / POW=. Genuine timeouts keep
their existing WaitForDmTimeout error, so the add-bond-invoice
happy-path branch (which only matches WaitForDmTimeout) is unaffected.

Closes #172

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@grunch, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 32 minutes and 38 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 357dd18d-6f9b-46bc-a16a-df57ecc8f439

📥 Commits

Reviewing files that changed from the base of the PR and between 8680419 and 2050955.

📒 Files selected for processing (3)
  • docs/pow_error_handling.md
  • src/util/events.rs
  • src/util/messaging.rs

Walkthrough

This PR implements detection and user-facing reporting of proof-of-work rejection errors in mostro-cli. When a mostrod instance requires NIP-13 PoW but the client's event lacks sufficient proof, the daemon silently drops the event, currently surfacing as a generic timeout. The PR introduces a new PowRequirementUnmet error type, refactors shared tag-reading helpers for kind-38385 info events, and integrates PoW-requirement checking into the wait_for_dm timeout path to distinguish semantic rejection from genuine timeouts.

Changes

PoW Rejection Error Handling

Layer / File(s) Summary
PoW Rejection Specification
docs/pow_error_handling.md
Design document defining the problem (daemon silently drops low-PoW events), solution (detect and report PowRequirementUnmet error), and integration points (wait_for_dm post-timeout, add_bond_invoice matching, best-effort fetch_required_pow from kind-38385). Includes acceptance criteria, unit and manual test plan, alternatives considered, and out-of-scope items.
Shared Info-Event Tag Reading
src/util/events.rs
Introduces read_info_tag_from_event pure helper to extract tag values from events and fetch_info_tag async wrapper to fetch newest replaceable kind-38385 event and apply tag extraction. Both fetch_bond_claim_window_days and fetch_required_pow refactored to use shared path, eliminating duplication while preserving best-effort None fallback. Unit tests verify tag extraction, missing-tag handling, and u8 parsing edge cases.
PowRequirementUnmet Error and wait_for_dm Integration
src/util/messaging.rs
Defines exported PowRequirementUnmet struct with required/configured u8 fields and implementations of Display (rendering PoW mismatch with --pow and POW= env references) and Error. Updates wait_for_dm to fetch required PoW on timeout, parse client POW env, and return PowRequirementUnmet when required > configured; otherwise returns WaitForDmTimeout. Unit tests verify error message content and downcast distinction.
Public API Updates
src/util/mod.rs
Re-export fetch_required_pow from events module and PowRequirementUnmet from messaging module with formatting line-wrap adjustments.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • MostroP2P/mostro-cli#169: Both PRs touch the same DM/wait-for-reply plumbing; #169's add_bond_invoice treats WaitForDmTimeout as the happy path, while this PR changes the wait_for_dm timeout path to potentially return PowRequirementUnmet instead.

Suggested reviewers

  • Catrya

Poem

🐰 A rabbit's ode to clarity in error:
When proof-of-work blocks the way,
No more "deadline" led astray—
Now users see the truth at play:
"Your PoW is too light, I say!" 🎯
With tags from kind-38385's light,
The daemon's need shines clear and bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: introducing a new explicit PoW rejection error instead of returning a generic timeout.
Linked Issues check ✅ Passed The PR implements all objectives from #172: differentiates PoW rejection from timeouts, adds PowRequirementUnmet error variant, detects daemon PoW requirements via kind-38385 info event, provides explicit user-facing messaging, and preserves genuine timeout behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing PoW rejection detection: specification documentation, helper functions for querying daemon PoW requirements, new error type, and test coverage for the feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pow-rejection-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86804192e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/util/messaging.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/util/messaging.rs`:
- Around line 344-361: The timeout branch currently awaits
super::events::fetch_required_pow(ctx) which can itself block up to
FETCH_EVENTS_TIMEOUT and double real timeout latency; change this so you do a
non-blocking/short-timeout probe instead: after Err(_elapsed) call
fetch_required_pow with a very short tokio::time::timeout (or poll it without
awaiting) and only if it returns Some(required) within that probe window compare
required against parse_pow_env() and potentially return PowRequirementUnmet; if
the probe times out or errors, immediately return WaitForDmTimeout without
waiting the full fetch timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2198022-0dc2-4934-8e30-216d195aaf22

📥 Commits

Reviewing files that changed from the base of the PR and between 5623f71 and 8680419.

📒 Files selected for processing (4)
  • docs/pow_error_handling.md
  • src/util/events.rs
  • src/util/messaging.rs
  • src/util/mod.rs

Comment thread src/util/messaging.rs
grunch and others added 2 commits May 28, 2026 13:17
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>
Previous attempt (commit 72ced79) capped the postflight info-event lookup
at 3s, but it still ran sequentially after the 15s DM wait timed out, so
the user-visible failure path could take up to 18s when the relay was
slow. Run the probe concurrently with the DM wait instead: by the time
the wait elapses the probe's answer is typically already in hand, so the
timeout branch consumes a resolved JoinHandle with ~0s added latency.
POW_PROBE_TIMEOUT is kept as a safety net for pathological relays that
outlive the 15s wait.

The probe needs a 'static future for tokio::spawn, so the work moves
into a new fetch_required_pow_with(client, mostro_pubkey) — the
existing fetch_required_pow(ctx) becomes a thin wrapper around it. On
the happy path (DM arrives in time) the spawned probe is aborted so we
don't leak a stray relay request.

Addresses review feedback on #173.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I reviewed the current head commit 2050955e919731722bbf50a0d70dc9b3a9413fa2.

This fixes the UX problem in the right place: wait_for_dm now keeps genuine timeouts distinct from silent PoW rejections, and the new PowRequirementUnmet error gives the user an explicit, actionable message instead of the misleading generic timeout.

I also checked the important add-bond-invoice interaction: it still only treats WaitForDmTimeout as the happy path, so PoW failures will correctly bubble up as errors instead of being misreported as success.

Checks run:

  • cargo test pow
  • cargo clippy --all-targets --all-features -- -D warnings

Both passed.

Copy link
Copy Markdown

@ermeme ermeme Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Comment

The PoW-rejection flow looks correct overall, and the test suite passes locally.
One non-blocking concern: on the timeout path, the code still waits up to 3s for the
PoW probe to finish before falling back to WaitForDmTimeout. That's much better than
the old sequential 15s+15s shape, but it can still add a small delay when the relay is
slow or the info event is stale. If you want the generic timeout to remain as close as
possible to 15s, consider shortening the probe budget further or serving a cached PoW
hint.

Copy link
Copy Markdown

@ermeme ermeme Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict Review

Verdict: APPROVED

I don't see a blocking issue in this patch. The change addresses the PoW-rejection path
without regressing the existing timeout flow, and the local test suite passes.

Minor note: the timeout path still has a small extra wait for the concurrent PoW probe,
but that's not a blocker for this PR.

@grunch grunch merged commit 25e07ae into main May 28, 2026
6 checks passed
@grunch grunch deleted the fix/pow-rejection-error branch May 28, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show explicit PoW rejection error instead of generic "deadline has elapsed" when mostrod requires proof of work

1 participant