Skip to content

Cli Refactor#1737

Merged
benalleng merged 3 commits into
payjoin:masterfrom
xstoicunicornx:cli-refactor
Jul 15, 2026
Merged

Cli Refactor#1737
benalleng merged 3 commits into
payjoin:masterfrom
xstoicunicornx:cli-refactor

Conversation

@xstoicunicornx

Copy link
Copy Markdown
Collaborator

This refactor covers 3 items, each which is addressed with a corresponding commit:

  • Encapsulate the expiry handling
  • Unify output messages
  • Drive state machine from central loop process

Expiry handling was updated to encapsulate the logic in a single, robust location which will eliminate potential variance at each call site.

Unifying the stdout messages is a nice enhancement that improves the readability of the cli and eliminates variance in the message format. This item is a follow up from #1700 (review).

Lastly, replaced the loops in the v2 sender and receiver flows with a driver: each step function performs one state transition and returns the next SendSession or ReceiveSession state, and process_sender_session/process_receiver_session loop over dispatch until the session terminates. This is now feasible because the transient errors return the next state to be processed. This item is a suggested follow up from #1724 (comment).

Supersedes #1734.

Claude Fable was used to assist in creating these code changes.

Pull Request Checklist

Please confirm the following before requesting review:

Expiry was handled in six places with three different message shapes:
bespoke finalize_expired_sender/receiver helpers, inline branches in
resume_payjoins, cancel_*_session, handle_error, and
long_poll_fallback. Delegate every expiry site to
cancel_sender_session/cancel_receiver_session, which already know how
to unwind every session typestate and both replay outcomes. Replay
re-detects expiry from the event log, so the in-memory typestate can
simply be dropped at the RelayPost::Expired sites.

The cancel functions' expired-replay branches now share one
finalize_expired_session helper that reports the fallback transaction
when the event log has one and closes the session either way.
Previously an expired session with no fallback (e.g. a receiver that
never got an original payload) returned an error without closing,
which would have made resume retry it forever.

Also fix two copy-paste bugs in cancel_receiver_session: it closed
with role "sender" and reported replay failures as sender sessions.
close_failed_session now takes the Role enum so the mismatch cannot
recur.
Session-scoped messages were inconsistent: some embedded the session
id mid-sentence, some named the role, and many ("Posted Original
PSBT...", "No response yet.") had neither, making interleaved output
from concurrently resumed sessions impossible to attribute.

Route all session-scoped printing through a print_session helper that
prefixes every line with "[<Role> <session id>]", padding the role to
the widest form so ids and messages align in columns. A SessionPrint
extension trait implemented for the sender and receiver persisters
lets call sites write persister.print(msg): the persister already
carries the session id, and its type determines the role, so a
message can never claim the wrong one. The resume completion loop and
finalize_expired_session print via the free helper since they do not
hold a typed persister at that point.

Raw payloads (BIP21 URI, fallback transaction hex) stay on their own
unprefixed lines for clean copy-paste. The "Payjoin sent" print moves
from the shared process_pj_response to its call sites so v2 can label
it while v1 output is unchanged. Receiver interrupt messaging now
gives the same resume/cancel instructions as the sender's.

Update e2e assertions that parsed the old formats: session ids are
read from the bracket prefix, and txid extraction uses the last token
rather than a fixed index.
Replace the loops in the v2 sender and
receiver flows with a driver: each step function performs one
state transition and returns the next SendSession or ReceiveSession
state, and process_sender_session/process_receiver_session loop over
dispatch until the session terminates.

Wrap process_receiver_session in a select against the interrupt
channel in receive_payjoin, mirroring how send_payjoin guards the
sender driver. This is safe at any phase: transitions persist to the
event log with no await between transition and save, so an
interrupted session resumes from its last saved state via the resume
command. The interrupt message now includes the session id, and
read_from_directory becomes a plain single-step function like the
other state handlers.
@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29369157815

Coverage increased (+0.4%) to 86.187%

Details

  • Coverage increased (+0.4%) from the base build.
  • Patch coverage: 89 uncovered changes across 1 file (156 of 245 lines covered, 63.67%).
  • 3 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
payjoin-cli/src/app/v2/mod.rs 245 156 63.67%

Coverage Regressions

3 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
payjoin-cli/src/app/v2/mod.rs 3 57.98%

Coverage Stats

Coverage Status
Relevant Lines: 15963
Covered Lines: 13758
Line Coverage: 86.19%
Coverage Strength: 345.45 hits per line

💛 - Coveralls

@benalleng benalleng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

TACK 3b3154d

I like the new cli prefixes, very nice! these I think these can even be useful carried over into other implementations for debug logs.

I'll concede on logic in aae2302 the consolidation seems sound, nice catch on the sender role bug

}
}

#[allow(clippy::incompatible_msrv)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: I don't think this is actually necessary

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same with send_payjoin but we can remove these another time

@benalleng benalleng merged commit 57c9dc6 into payjoin:master Jul 15, 2026
13 checks passed
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.

3 participants