Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only derive JsonError for errors that can return Json #526

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

DanGould
Copy link
Contributor

@DanGould DanGould commented Feb 4, 2025

Re: #403

@DanGould DanGould force-pushed the recv-err branch 2 times, most recently from 553a644 to be89d64 Compare February 4, 2025 20:01
@coveralls
Copy link
Collaborator

coveralls commented Feb 4, 2025

Pull Request Test Coverage Report for Build 13208094848

Details

  • 135 of 170 (79.41%) changed or added relevant lines in 9 files are covered.
  • 10 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.3%) to 79.276%

Changes Missing Coverage Covered Lines Changed/Added Lines %
payjoin/src/receive/v1/exclusive/error.rs 0 1 0.0%
payjoin/src/receive/v2/mod.rs 35 36 97.22%
payjoin/src/receive/v1/exclusive/mod.rs 27 29 93.1%
payjoin-cli/src/app/v1.rs 21 27 77.78%
payjoin-cli/src/app/v2.rs 14 20 70.0%
payjoin/src/receive/v1/mod.rs 17 23 73.91%
payjoin/src/receive/error.rs 6 19 31.58%
Files with Coverage Reduction New Missed Lines %
payjoin-cli/src/app/v2.rs 1 75.9%
payjoin-cli/src/app/v1.rs 4 78.46%
payjoin/src/receive/error.rs 5 16.91%
Totals Coverage Status
Change from base Build 13203244614: 0.3%
Covered Lines: 4028
Relevant Lines: 5081

💛 - Coveralls

@DanGould DanGould force-pushed the recv-err branch 2 times, most recently from b09d837 to fd5274f Compare February 5, 2025 18:03
@DanGould
Copy link
Contributor Author

DanGould commented Feb 5, 2025

My refactor separates replyable from unreplyable errors. The biggest hurdle I've run into is when the payload gets parsed as a string before validate_payload. That prevents the PayloadError::Utf8 from being shared between v1 and v2 payload parsing even though the receiver has the sender's reply key at this point and could in fact reply to the sender. Going to think for a moment about whether or not a different format could be used to serialize the psbt || \n || query payload that'd get around this problem. Perhaps even serializing the query as a field of a strict byte length in message_a instead of in the string payload.

@DanGould DanGould force-pushed the recv-err branch 2 times, most recently from bd9620e to 758b9cd Compare February 5, 2025 18:10
@DanGould DanGould requested a review from spacebear21 February 5, 2025 18:35
@DanGould DanGould marked this pull request as ready for review February 5, 2025 18:35
Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

What do you think about naming the variants RecoverableError/UnrecoverableError instead of Un/Replyable? My brain keeps auto-correcting that to "replayable".

url::form_urlencoded::parse(query.as_bytes()),
SUPPORTED_VERSIONS,
)
.map_err(InternalPayloadError::SenderParams)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

on line 182 before this change, there is a log::debug! that is now duplicated in validate_payload.

payjoin/src/receive/mod.rs Show resolved Hide resolved
@@ -16,7 +16,7 @@ use hyper_util::rt::TokioIo;
use payjoin::bitcoin::psbt::Psbt;
use payjoin::bitcoin::{self, FeeRate};
use payjoin::receive::v1::{PayjoinProposal, UncheckedProposal};
use payjoin::receive::Error;
use payjoin::receive::ReplyableError;
Copy link
Collaborator

Choose a reason for hiding this comment

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

For brevity I prefer importing ReplyableError::{V1, Implementation} and using the variants directly (like it's done in app/v2.rs).

payjoin-cli/src/app/v2.rs Outdated Show resolved Hide resolved
The abstraction simplifies and categorizes error handling
@DanGould
Copy link
Contributor Author

DanGould commented Feb 6, 2025

I've changed the design again.

This time, Error is dividend into non_exhaustive Recoverable and V2 variants. I note that I do still prefer Replyable since some of the V2 variants are recoverable in the sense that they just require different configuration, they don't cause the program to crash. Jsonable or ReturnToSender names could also work.

I removed the Unrecoverable variant since it only covered v2::SessionError types, and the receive::Error type is almost certainly going to be extended with more multiparty variants.

I'm not sure if it makes sense for the v1 module to return RecoverableError, as it does now, since all the errors are indeed recoverable, or if it should be wrapped in the single receive::Error layer since that would let it match the other v2 module.

@spacebear21 please let me know what you think. I do indeed believe this is much closer to a stable reality based on actual hierarchy than it was before, especially since the fundamental difference between errors that may be returned to the sender is separated.

Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

I'm ok with ReturnToSender or maybe Returnable.

I'm not sure if it makes sense for the v1 module to return RecoverableError

I'm not sure exactly where you mean - in extract_proposal_from_v1? I'm fine with returning the lowest common denominator (RecoverableError) in v1 functions and letting callers map to Error if needed.

On another note, I wonder if we should box a ImplementationError = Box<dyn Error + Send + Sync> type so that the receiver function signatures can be more semantically correct:

e.g.

        pub fn identify_receiver_outputs(
        self,
        is_receiver_output: impl Fn(&Script) -> Result<bool, ImplementationError>,
    ) -> Result<WantsOutputs, RecoverableError> {

Comment on lines 6 to 7
#[cfg(feature = "v1")]
use crate::receive::v1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like it could be removed too in favor of:

#[cfg(feature = "v1")]
V1(crate::receive::v1::RequestError),

@DanGould
Copy link
Contributor Author

DanGould commented Feb 7, 2025

Regarding names, I took a happy medium keeping ReplyableError but using the ReplyToSender variant.

Re:

we should box a ImplementationError = Box<dyn Error + Send + Sync> type so that the receiver function signatures can be more semantically correct:

This is an incredible idea! I was playing around with returning Box from these functions yesterday but didn't quite discover that an alias is how to make this semantically correct. Added it as a third commit and it looks great, way easier to implement the receive state machine properly by using ImplementationError::from.

Now I'm wondering how to handle the straggling receive errors: SelectionError, OutputSubstitutionError, InputContributionError. If unrecoverable, they're probably all ReplyableError variants with an unavailable or not enough money error code. I think I know how to address it in a follow up.

Only some errors are actually recoverable and must return JSON. Fix payjoin#522.
It's more semantically correct to show that closures return a Boxed error
than expecting a specific variant of ReplyableError
Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

ACK 1358c50

Now I'm wondering how to handle the straggling receive errors: SelectionError, OutputSubstitutionError, InputContributionError. If unrecoverable, they're probably all ReplyableError variants with an unavailable or not enough money error code. I think I know how to address it in a follow up.

I think these are all technically recoverable from the receiver side, e.g. "try again with a different input selection". If the receiver can't handle them then they become unavailable in most (all?) cases. I'm not sure we should use the not-enough-money error code for InternalInputContributionError::ValueTooLow because BIP78 says it specifically applies to "could not bump the fee of the payjoin proposal. "

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.

receive::v2::SessionError variants are not recoverable and must not return JSON
3 participants