Skip to content

Cli cancel UX#1700

Merged
benalleng merged 4 commits into
payjoin:masterfrom
benalleng:cli-UI-additions
Jul 14, 2026
Merged

Cli cancel UX#1700
benalleng merged 4 commits into
payjoin:masterfrom
benalleng:cli-UI-additions

Conversation

@benalleng

Copy link
Copy Markdown
Collaborator

This PR builds on #1688 but reverts the completed_at DB column removal.

  • Add an auto-cancel mid loop when passing the expiry instead of only on process start.
  • print the session ID at the start of the session to better keep track if the user want to immediately cancel.
  • Show what sessions have a fallback transaction available from payjoin-cli history for rebroadcasting.

example output. custom timeout of 20s to demonstrate automatic cancel logic
image

Coded with help from GLM-5.2

Pull Request Checklist

Please confirm the following before requesting review:

@benalleng
benalleng requested a review from spacebear21 June 30, 2026 17:26
@benalleng

Copy link
Copy Markdown
Collaborator Author

@spacebear21 I am most curious of your thoughts on 9b718ac and whether you think this is appropriate for both the sender and receiver

@coveralls

coveralls commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29342380627

Coverage decreased (-0.2%) to 85.777%

Details

  • Coverage decreased (-0.2%) from the base build.
  • Patch coverage: 80 uncovered changes across 2 files (63 of 143 lines covered, 44.06%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
payjoin-cli/src/app/v2/mod.rs 121 42 34.71%
payjoin/src/core/error.rs 10 9 90.0%
Total (5 files) 143 63 44.06%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
payjoin-cli/src/app/v2/mod.rs 1 49.29%

Coverage Stats

Coverage Status
Relevant Lines: 15981
Covered Lines: 13708
Line Coverage: 85.78%
Coverage Strength: 344.95 hits per line

💛 - Coveralls

@bc1cindy bc1cindy left a comment

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.

looks solid

two notes inline on the expiry paths.

the new mid-loop cancel/broadcast and the fallback_available display aren't covered by tests

adding a test driving an expired session through the sender path would lock in the behavior, wdyt?

Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
@benalleng
benalleng force-pushed the cli-UI-additions branch 4 times, most recently from c91653e to 597ffe4 Compare July 2, 2026 18:46
@benalleng
benalleng requested a review from bc1cindy July 2, 2026 19:58
@benalleng
benalleng force-pushed the cli-UI-additions branch 2 times, most recently from b241374 to 8d063a0 Compare July 2, 2026 20:20
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
@benalleng
benalleng force-pushed the cli-UI-additions branch 3 times, most recently from 02f8cb5 to 7f0c249 Compare July 3, 2026 21:38
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment on lines +771 to +772
println!("Error: Sender session expired: {}", persister.session_id());
Ok(())

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.

"Error: Sender session expired" printed here but the fn returns Ok(())

@DanGould

DanGould commented Jul 7, 2026

Copy link
Copy Markdown
Member

The issue with automating fallback is perhaps that another session could spend the same coin between sessions. For example if bitcoind was restarted and another payjoin session spent the same coin. Then you might have a fallback automatically broadcast that doesn't pay attention to wallet state? Just seems like some complexity that this would need to take responsibility for. Not completely confident in the motivation and my tendency is to prefer leaving things manual unless the automation is a no brainer default.

@benalleng
benalleng force-pushed the cli-UI-additions branch 4 times, most recently from 9de5aeb to a4e6f34 Compare July 8, 2026 14:20
@benalleng
benalleng requested a review from xstoicunicornx July 8, 2026 14:30

@xstoicunicornx xstoicunicornx 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.

General nit: a lot of the sender/receiver verbiage has been removed from tracing and print statements, was there a reason for this? Maybe I can take as a followup - as I have been wanting to standardize the printing more in the CLI so that every printed line starts with the session role and ID.

Rather than creating repetitive relay selection/expiration code (and in some places loosing the loop it seems) why not adapt post_via_relay to still be usable in all contexts? For example (code created with Claude Opus 4.8):

/// A request-construction error that can report whether it was caused by
/// session expiry. Implemented by the sender/receiver request-building error
/// types so `post_via_relay` can hand expiry back to the caller (which owns the
/// typestate needed to react) instead of flattening it into `anyhow::Error`.
trait RequestExpiry {
    fn expired(&self) -> bool;
}

impl RequestExpiry for payjoin::send::v2::CreateRequestError {
    fn expired(&self) -> bool { self.is_expired() }
}

impl RequestExpiry for payjoin::receive::v2::CreateRequestError {
    fn expired(&self) -> bool { self.is_expired() }
}

impl RequestExpiry for payjoin::receive::v2::SessionError {
    fn expired(&self) -> bool { self.is_expired() }
}

/// Outcome of building and posting a request via `post_via_relay`. HTTP
/// failures are retried against other relays inside the helper; only session
/// expiry and fatal build errors escape to the caller.
enum RelayPost<T> {
    Posted(reqwest::Response, T),
    Expired,
}

and then:

    async fn post_via_relay<F, T, E>(&self, mut build: F) -> Result<RelayPost<T>>
    where
        F: FnMut(&str) -> std::result::Result<(payjoin::Request, T), E>,
        E: RequestExpiry + Into<anyhow::Error>,
    {
        loop {
            let relay = self.mailroom_manager.choose_relay()?;
            let (req, ctx) = match build(relay.as_str()) {
                Ok(r) => r,
                Err(e) if e.expired() => return Ok(RelayPost::Expired),
                Err(e) => return Err(e.into()),
            };
            match self.post_request(req).await {
                Ok(resp) => return Ok(RelayPost::Posted(resp, ctx)),
                Err(e) => {
                    tracing::debug!("Request to relay {relay} failed: {e:?}");
                    self.mailroom_manager.add_failed_relay(relay);
                }
            }
        }
    }
}

See full (half-reviewed) implementation here in last commit https://github.com/xstoicunicornx/rust-payjoin/tree/pr1700-mod

Comment thread payjoin-cli/tests/e2e.rs Outdated
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment thread payjoin/src/core/error.rs Outdated
@benalleng

Copy link
Copy Markdown
Collaborator Author

Maybe I can take as a followup - as I have been wanting to standardize the printing more in the CLI so that every printed line starts with the session role and ID.

I'm fine dropping the last commit if you want to take this up. I've gotten a bit lost in the sauce having redesigned this PR and I'm not sure what I think is best for the readability

@benalleng
benalleng force-pushed the cli-UI-additions branch 4 times, most recently from 28f611c to 84bea22 Compare July 9, 2026 19:06
@spacebear21 spacebear21 added this to the payjoin-cli-1.0 milestone Jul 10, 2026

@xstoicunicornx xstoicunicornx 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.

Few more questions but overall looking good.

Comment thread payjoin-cli/src/app/v2/mod.rs
Comment thread payjoin-cli/src/app/v2/mod.rs
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Before this change there was no reasonable way of knowing which session
had a fallback transaction available meaning that if the session was
closed with `payjoin-cli cancel # --no-broadcast` a user would have to enumerate
through all the sessions trying to look for which session had the
fallback tx calling back the sessions with `payjoin-cli history`.
This ensures that the session id is printed immediately after session id
creation ensuring that the session id is displayed at the earliest
moment it is readable.
@benalleng
benalleng force-pushed the cli-UI-additions branch 5 times, most recently from eaa357a to 2792915 Compare July 13, 2026 20:10
@benalleng

Copy link
Copy Markdown
Collaborator Author

Got caught up a bit in rebase hell on this one, thanks for being diligent in your reveiws @xstoicunicornx

I think this is good for a re-review

@benalleng
benalleng requested a review from xstoicunicornx July 13, 2026 20:12

@xstoicunicornx xstoicunicornx 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.

A couple more things I noticed. The comment in finalize_expired_receiver is just a suggestion but there are other ways to implement that don't require as much of a refactor.

Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
Comment thread payjoin-cli/src/app/v2/mod.rs
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
RelayPost::Posted(resp, ctx) => (resp, ctx),
RelayPost::Expired => {
let session_id = persister.session_id();
Self::close_failed_session(persister, &session_id, "receiver");

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.

I think this needs to handle sessions that have a fallback tx.

@benalleng
benalleng requested a review from xstoicunicornx July 14, 2026 14:38
Comment thread payjoin-cli/src/app/v2/mod.rs Outdated
"Session {session_id} failed to deliver error reply. Broadcast the fallback transaction manually:\n{}",
serialize_hex(pending.fallback_tx())
);
pending.close().save(persister)?;

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.

Shouldn't we be avoiding closing automatically?

Previously the cli only checked for expiry at program start during long
polling loops there was not an expiry check leading to the possiblity of
an indefinite poll past expiry where the counterparty had already given
up. This will automatically close the session now whenever the expiry
time is crossed.

This also adds an `--expire-in` flag for the receiver to both test this
functionality and demonstrate that the receiver is not actually fixed to
a 24hr expiration and is in controll of this expiry time.
This moves all session_id prints to be formatted the same way for easier
readability and copying.

Also fixes some stale print regarding resuming using `resume
--session-id` instead of `send`.

Lastly fixes the history role column to be only as wide as it needs to
be.

@xstoicunicornx xstoicunicornx 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.

utACK 2201bdc

Reviewed and tested the code. This implements expiry handling at every point where expiry could get hit rather than just upon startup.

Will be taking on followup to standardize the messages printed to the user.

@benalleng
benalleng merged commit f603b8b into payjoin:master Jul 14, 2026
13 checks passed
@xstoicunicornx xstoicunicornx mentioned this pull request Jul 14, 2026
2 tasks
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.

6 participants