Skip to content

Persist payjoin sender session and resume on cold restart#809

Open
Sandipmandal25 wants to merge 8 commits into
masterfrom
feat/payjoin-sender-persistence
Open

Persist payjoin sender session and resume on cold restart#809
Sandipmandal25 wants to merge 8 commits into
masterfrom
feat/payjoin-sender-persistence

Conversation

@Sandipmandal25

@Sandipmandal25 Sandipmandal25 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

summary

this pr makes payjoin sender sessions recoverable after a cold restart.

the payjoin sender pr (#772) used NoopSessionPersister, so any in flight payjoin session was lost if the app restarted before the negotiation finished. this implements a real SessionPersister backed by redb.

changes

the session event log is now persisted before the first network request, along with the consensus-encoded fallback transaction. on startup, the stored event log is replayed so the app can continue the payjoin flow safely.

depending on the recovered state, the sender can now resume polling, broadcast an already completed proposal, or fall back to the original transaction if the session cannot be reconstructed.

Checklist

Summary by CodeRabbit

  • New Features

    • Payjoin sessions now persist across app restarts, letting in-progress send flows resume automatically.
    • Added transaction lookup support (mempool-or-chain) to improve terminal handling during send recovery.
  • Bug Fixes

    • Improved recovery for saved payjoin sessions, including more reliable fallback/proposal resumption and protection against corrupt saved state.
    • More consistent session cleanup and safer retry behavior when persistence or broadcasting fails.

@Sandipmandal25 Sandipmandal25 self-assigned this Jun 25, 2026
@Sandipmandal25 Sandipmandal25 added the SoB-2026 Project for Summer of Bitcoin 2026 label Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4512a5c-e381-42c1-8bde-46f0c2166f79

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Wallet data now stores payjoin sender sessions, the payjoin actor persists session state and handles terminal outcomes, and wallet startup can resume stored sessions or replay their saved fallback transaction.

Changes

Persisted Payjoin Session Flow

Layer / File(s) Summary
Wallet data session record
rust/src/database/wallet_data.rs, android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
Adds PayjoinSenderSession, maps it to payjoin_sender_session, exposes WalletDataDb get/set/delete helpers, and extends Android serialization support for the new key.
Persisted sender and polling state
rust/src/manager/wallet_manager/payjoin.rs
Adds PayjoinSessionPersister, threads it through sender construction, polling, fallback completion, and PayjoinActor state, and adds persister/resumption tests.
Initiation and terminal handling
rust/src/node/client.rs, rust/src/node/client/electrum.rs, rust/src/node/client/esplora.rs, rust/src/manager/wallet_manager/actor/transactions.rs
Adds transaction lookup support and uses it to gate payjoin initiation, persist recovery intent before sender setup, and handle fallback or terminal broadcast outcomes with session retention and cleanup.
Startup resumption
rust/src/manager/wallet_manager/actor.rs, rust/src/manager/wallet_manager/payjoin.rs
WalletActor::started now triggers payjoin resumption, and resume_session replays stored events or falls back to the saved transaction when replay fails or the record is corrupt.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Possibly related PRs

  • bitcoinppl/cove#772: Implements the PayJoin v2 sender and wallet-manager actor flow that this PR extends with persisted session storage, recovery, and startup resumption.

Poem

I tucked my payjoin hops in hay,
And saved my trail from day to day.
If moonlit paths must start anew,
I’ll replay crumbs and carry through.
This bunny’s flow still finds its way. 🐰

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning It covers summary, changes, and checklist, but is missing the required Testing and Platform Coverage sections. Add a Testing section with commands or rationale, and complete Platform Coverage with the applicable device/simulator checkboxes.
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: persisting payjoin sessions and resuming after restart.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/payjoin-sender-persistence

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.

@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@coderabbitai

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@Sandipmandal25: I’ll review the PR now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes payjoin sender sessions recover after an app restart. The main changes are:

  • Persisted payjoin event logs and fallback transactions in wallet data.
  • Startup recovery for polling, proposal broadcast, fallback broadcast, and unrecoverable saved state.
  • Terminal broadcast cleanup that keeps recovery state until wallet persistence succeeds.
  • Node transaction lookup for already-submitted proposal or fallback transactions.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
rust/src/database/wallet_data.rs Adds persisted payjoin sender-session records with event logs, fallback transactions, creation time, and terminal action markers.
rust/src/manager/wallet_manager/payjoin.rs Adds the redb-backed payjoin session persister and startup replay logic for saved sender sessions.
rust/src/manager/wallet_manager/actor.rs Dispatches recovered payjoin sessions during wallet startup.
rust/src/manager/wallet_manager/actor/transactions.rs Adds payjoin send gating, terminal marker persistence, terminal broadcast recovery, and cleanup ordering.
rust/src/node/client.rs Adds a shared transaction lookup method for node clients.
rust/src/node/client/electrum.rs Adds Electrum transaction lookup with not-found handling.
rust/src/node/client/esplora.rs Adds Esplora transaction lookup for recovery checks.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Start payjoin send] --> B[Persist session and fallback tx]
  B --> C[Save payjoin events]
  C --> D{App restarts?}
  D -->|No| E[Continue normal negotiation]
  D -->|Yes| F[Replay stored session]
  F -->|Polling and not expired| G[Resume polling]
  F -->|Stored proposal marker| H[Broadcast stored proposal]
  F -->|Stored fallback marker| I[Broadcast stored fallback]
  F -->|Closed success| J[Sign recovered proposal]
  F -->|Expired or failed replay| I
  J --> K[Persist proposal marker]
  K --> H
  H --> L[Finalize terminal tx]
  I --> L
  L --> M[Persist wallet]
  M --> N[Delete payjoin session]
  L -->|Broadcast or persist fails| O[Keep session for retry]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
  A[Start payjoin send] --> B[Persist session and fallback tx]
  B --> C[Save payjoin events]
  C --> D{App restarts?}
  D -->|No| E[Continue normal negotiation]
  D -->|Yes| F[Replay stored session]
  F -->|Polling and not expired| G[Resume polling]
  F -->|Stored proposal marker| H[Broadcast stored proposal]
  F -->|Stored fallback marker| I[Broadcast stored fallback]
  F -->|Closed success| J[Sign recovered proposal]
  F -->|Expired or failed replay| I
  J --> K[Persist proposal marker]
  K --> H
  H --> L[Finalize terminal tx]
  I --> L
  L --> M[Persist wallet]
  M --> N[Delete payjoin session]
  L -->|Broadcast or persist fails| O[Keep session for retry]
Loading

Reviews (9): Last reviewed commit: "add actor-level tests for payjoin send g..." | Re-trigger Greptile

Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/src/manager/wallet_manager/payjoin.rs (1)

412-413: 🗄️ Data Integrity & Integration | 🟠 Major

Persist the POST transition before sending the request

create_v2_post_request() runs before sender.process_response(&post_response, post_ctx).save(&persister) commits the new state, so a crash in that window leaves only the pre-POST session on disk and a restart can resend the original PSBT. Persist an in-flight/pre-send state first, or handle this restart path without re-posting.

🤖 Prompt for 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.

In `@rust/src/manager/wallet_manager/payjoin.rs` around lines 412 - 413, The POST
transition in payjoin flow is being persisted too late, after the request is
already sent, which can leave only the pre-POST session on disk if the process
crashes. Update the flow around create_v2_post_request() and
sender.process_response(&post_response, post_ctx).save(&persister) so the
in-flight/pre-send state is saved before the POST is issued, or make the restart
logic in this path safe to resume without resending the original PSBT.
🤖 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 `@rust/src/database/wallet_data.rs`:
- Around line 84-89: Persist the session deadline in PayjoinSenderSession so
restarts don’t reset the timeout. Update the PayjoinSenderSession record in
wallet_data.rs to include an absolute created-at or expires-at timestamp
alongside events and fallback_tx, then change the resume logic that currently
rebuilds the deadline from Instant::now() + PAYJOIN_SESSION_TIMEOUT to derive
remaining time from the stored timestamp and immediately fall back when the
session is already expired.

In `@rust/src/manager/wallet_manager/actor/transactions.rs`:
- Around line 662-665: Make the payjoin cleanup in handle_payjoin_success
idempotent by ensuring the persisted session is cleared on every terminal
success path, not just after the main broadcast succeeds. Update the
broadcast_and_notify fallback flow and the proposal-broadcast result handling in
transactions.rs so that “already known/already broadcast” proposal outcomes are
treated as terminal success before any fallback is attempted, and
delete_payjoin_sender_session is still invoked for those accepted terminal
cases.

---

Outside diff comments:
In `@rust/src/manager/wallet_manager/payjoin.rs`:
- Around line 412-413: The POST transition in payjoin flow is being persisted
too late, after the request is already sent, which can leave only the pre-POST
session on disk if the process crashes. Update the flow around
create_v2_post_request() and sender.process_response(&post_response,
post_ctx).save(&persister) so the in-flight/pre-send state is saved before the
POST is issued, or make the restart logic in this path safe to resume without
resending the original PSBT.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6d4e1ed-8eb4-4ea6-86ef-8a493853342f

📥 Commits

Reviewing files that changed from the base of the PR and between f120549 and 134fac6.

📒 Files selected for processing (4)
  • rust/src/database/wallet_data.rs
  • rust/src/manager/wallet_manager/actor.rs
  • rust/src/manager/wallet_manager/actor/transactions.rs
  • rust/src/manager/wallet_manager/payjoin.rs

Comment thread rust/src/database/wallet_data.rs
Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from d4fa3d4 to 47b72ad Compare June 25, 2026 23:16
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated
Comment thread rust/src/manager/wallet_manager/actor/transactions.rs
@praveenperera praveenperera changed the title persist payjoin sender session and resume on cold restart Persist payjoin sender session and resume on cold restart Jun 25, 2026
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from 89ac6d5 to f45a931 Compare June 26, 2026 01:15
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread rust/src/manager/wallet_manager/actor.rs Outdated
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch from 9290c80 to f4e736b Compare June 26, 2026 01:30
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

@bitcoinppl bitcoinppl deleted a comment from coderabbitai Bot Jun 26, 2026
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@coderabbitai re review

Comment thread rust/src/manager/wallet_manager/actor.rs
Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@rust/src/manager/wallet_manager/actor.rs`:
- Around line 190-191: The `resume_session`/`SessionResumption` flow currently
collapses missing session and recovery failures into `SessionResumption::None`,
so `WalletManagerActor` silently no-ops in `actor.rs`. Update `resume_session`
to distinguish “no persisted session” from read/corruption failures by returning
a typed error outcome for failures, then handle that error in the `match` in
`WalletManagerActor` and route it through `notify_payjoin_error` instead of
treating it like `None`.

In `@rust/src/manager/wallet_manager/actor/transactions.rs`:
- Around line 716-728: Don’t emit the payjoin “complete” signal when cleanup
fails: in the transaction flow around self.db.delete_payjoin_sender_session(), a
failed cleanup currently still leads to sending Msg::PayjoinTxBroadcast even
though the session record remains and will block future sends. Update the
handling in this branch so the failure is surfaced as a cleanup/retry error or
the completion message is withheld until the retained session is actually
cleared, using the existing self.db.delete_payjoin_sender_session() and
Msg::PayjoinTxBroadcast path to locate the fix.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c4e5fd8b-ea81-4f00-93c5-083401996c0f

📥 Commits

Reviewing files that changed from the base of the PR and between ef47af0 and 0583ae1.

⛔ Files ignored due to path filters (1)
  • ios/CoveCore/Sources/CoveCore/generated/cove.swift is excluded by !**/generated/**
📒 Files selected for processing (8)
  • android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
  • rust/src/database/wallet_data.rs
  • rust/src/manager/wallet_manager/actor.rs
  • rust/src/manager/wallet_manager/actor/transactions.rs
  • rust/src/manager/wallet_manager/payjoin.rs
  • rust/src/node/client.rs
  • rust/src/node/client/electrum.rs
  • rust/src/node/client/esplora.rs

Comment thread rust/src/manager/wallet_manager/actor.rs
Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from a535f2e to 95f4499 Compare June 26, 2026 10:58
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread rust/src/manager/wallet_manager/payjoin.rs
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch from 95f4499 to c379946 Compare June 29, 2026 20:30
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from 44b37ce to 537014c Compare June 29, 2026 20:41
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

@praveenperera praveenperera force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from 7eb718d to 75a985d Compare June 30, 2026 15:47
Comment thread rust/src/manager/wallet_manager/actor.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
Comment thread rust/src/manager/wallet_manager/payjoin.rs
Comment thread rust/src/node/client/electrum.rs Outdated
Comment thread rust/src/manager/wallet_manager/actor/transactions.rs Outdated

if !can_cleanup {
return Produces::ok(Err(Error::SignAndBroadcastError(
"a previous payjoin session is pending recovery; please try again later"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Longer-term, this probably needs a user-visible recovery/abandon path. If the committed terminal tx is permanently rejected by the network, startup will keep retrying, get_transaction will keep saying it is not known, and this gate leaves the wallet unable to send with no way out other than manual database surgery.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

will track this as a follow up for a cancel button.

Comment thread rust/src/database/wallet_data.rs Outdated
@Sandipmandal25 Sandipmandal25 marked this pull request as draft June 30, 2026 21:27
@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 3 times, most recently from f76562a to 04a49d7 Compare July 1, 2026 05:40
@Sandipmandal25 Sandipmandal25 marked this pull request as ready for review July 1, 2026 05:44
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

@Sandipmandal25 Sandipmandal25 force-pushed the feat/payjoin-sender-persistence branch 2 times, most recently from 704037a to af441ca Compare July 1, 2026 06:19
@praveenperera praveenperera force-pushed the feat/payjoin-sender-persistence branch from af441ca to a538fd8 Compare July 3, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

SoB-2026 Project for Summer of Bitcoin 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants