Skip to content

Commit 33a58f7

Browse files
committed
Add enum ErrorCodes
Well-known error codes are used on both sides of the protocol. Having a strong type will let us serialize an error Reply across a foreign language boundary. We were returning a non-spec sender-params-error which amounts to a rejection of the original PSBT for unparseable feerate. Instead, I decided to return the well-known original-psbt-rejected error code and to include the parse error in the debug message in order to comply more closely with existing spec.
1 parent 6f356cd commit 33a58f7

File tree

4 files changed

+76
-45
lines changed

4 files changed

+76
-45
lines changed

payjoin/src/error_codes.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
//! Well-known error codes as defined in BIP-78
22
//! See: <https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#receivers-well-known-errors>
33
4-
/// The payjoin endpoint is not available for now.
5-
pub const UNAVAILABLE: &str = "unavailable";
4+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5+
pub enum ErrorCode {
6+
/// The payjoin endpoint is not available for now.
7+
Unavailable,
8+
/// The receiver added some inputs but could not bump the fee of the payjoin proposal.
9+
NotEnoughMoney,
10+
/// This version of payjoin is not supported.
11+
VersionUnsupported,
12+
/// The receiver rejected the original PSBT.
13+
OriginalPsbtRejected,
14+
}
615

7-
/// The receiver added some inputs but could not bump the fee of the payjoin proposal.
8-
pub const NOT_ENOUGH_MONEY: &str = "not-enough-money";
16+
impl ErrorCode {
17+
pub const fn as_str(&self) -> &'static str {
18+
match self {
19+
Self::Unavailable => "unavailable",
20+
Self::NotEnoughMoney => "not-enough-money",
21+
Self::VersionUnsupported => "version-unsupported",
22+
Self::OriginalPsbtRejected => "original-psbt-rejected",
23+
}
24+
}
25+
}
926

10-
/// This version of payjoin is not supported.
11-
pub const VERSION_UNSUPPORTED: &str = "version-unsupported";
27+
impl core::str::FromStr for ErrorCode {
28+
type Err = ();
1229

13-
/// The receiver rejected the original PSBT.
14-
pub const ORIGINAL_PSBT_REJECTED: &str = "original-psbt-rejected";
30+
fn from_str(s: &str) -> Result<Self, Self::Err> {
31+
match s {
32+
"unavailable" => Ok(Self::Unavailable),
33+
"not-enough-money" => Ok(Self::NotEnoughMoney),
34+
"version-unsupported" => Ok(Self::VersionUnsupported),
35+
"original-psbt-rejected" => Ok(Self::OriginalPsbtRejected),
36+
_ => Err(()),
37+
}
38+
}
39+
}
40+
41+
impl core::fmt::Display for ErrorCode {
42+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43+
f.write_str(self.as_str())
44+
}
45+
}

payjoin/src/receive/error.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{error, fmt};
22

3-
use crate::error_codes::{
4-
NOT_ENOUGH_MONEY, ORIGINAL_PSBT_REJECTED, UNAVAILABLE, VERSION_UNSUPPORTED,
3+
use crate::error_codes::ErrorCode::{
4+
self, NotEnoughMoney, OriginalPsbtRejected, Unavailable, VersionUnsupported,
55
};
66

77
pub type ImplementationError = Box<dyn error::Error + Send + Sync>;
@@ -82,17 +82,17 @@ impl JsonError for ReplyableError {
8282
Self::Payload(e) => e.to_json(),
8383
#[cfg(feature = "v1")]
8484
Self::V1(e) => e.to_json(),
85-
Self::Implementation(_) => serialize_json_error(UNAVAILABLE, "Receiver error"),
85+
Self::Implementation(_) => serialize_json_error(Unavailable, "Receiver error"),
8686
}
8787
}
8888
}
8989

90-
pub(crate) fn serialize_json_error(code: &str, message: impl fmt::Display) -> String {
90+
pub(crate) fn serialize_json_error(code: ErrorCode, message: impl fmt::Display) -> String {
9191
format!(r#"{{ "errorCode": "{}", "message": "{}" }}"#, code, message)
9292
}
9393

9494
pub(crate) fn serialize_json_plus_fields(
95-
code: &str,
95+
code: ErrorCode,
9696
message: impl fmt::Display,
9797
additional_fields: &str,
9898
) -> String {
@@ -185,29 +185,29 @@ impl JsonError for PayloadError {
185185
use InternalPayloadError::*;
186186

187187
match &self.0 {
188-
Utf8(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
189-
ParsePsbt(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
188+
Utf8(_) => serialize_json_error(OriginalPsbtRejected, self),
189+
ParsePsbt(_) => serialize_json_error(OriginalPsbtRejected, self),
190190
SenderParams(e) => match e {
191191
super::optional_parameters::Error::UnknownVersion { supported_versions } => {
192192
let supported_versions_json =
193193
serde_json::to_string(supported_versions).unwrap_or_default();
194194
serialize_json_plus_fields(
195-
VERSION_UNSUPPORTED,
195+
VersionUnsupported,
196196
"This version of payjoin is not supported.",
197197
&format!(r#""supported": {}"#, supported_versions_json),
198198
)
199199
}
200-
_ => serialize_json_error("sender-params-error", self),
200+
_ => serialize_json_error(OriginalPsbtRejected, self),
201201
},
202-
InconsistentPsbt(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
203-
PrevTxOut(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
204-
MissingPayment => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
205-
OriginalPsbtNotBroadcastable => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
206-
InputOwned(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
207-
InputWeight(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
208-
InputSeen(_) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
209-
PsbtBelowFeeRate(_, _) => serialize_json_error(ORIGINAL_PSBT_REJECTED, self),
210-
FeeTooHigh(_, _) => serialize_json_error(NOT_ENOUGH_MONEY, self),
202+
InconsistentPsbt(_) => serialize_json_error(OriginalPsbtRejected, self),
203+
PrevTxOut(_) => serialize_json_error(OriginalPsbtRejected, self),
204+
MissingPayment => serialize_json_error(OriginalPsbtRejected, self),
205+
OriginalPsbtNotBroadcastable => serialize_json_error(OriginalPsbtRejected, self),
206+
InputOwned(_) => serialize_json_error(OriginalPsbtRejected, self),
207+
InputWeight(_) => serialize_json_error(OriginalPsbtRejected, self),
208+
InputSeen(_) => serialize_json_error(OriginalPsbtRejected, self),
209+
PsbtBelowFeeRate(_, _) => serialize_json_error(OriginalPsbtRejected, self),
210+
FeeTooHigh(_, _) => serialize_json_error(NotEnoughMoney, self),
211211
}
212212
}
213213
}

payjoin/src/receive/v1/exclusive/error.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ impl JsonError for RequestError {
4242
fn to_json(&self) -> String {
4343
use InternalRequestError::*;
4444

45+
use crate::error_codes::ErrorCode::OriginalPsbtRejected;
4546
use crate::receive::error::serialize_json_error;
4647
match &self.0 {
47-
Io(_) => serialize_json_error("original-psbt-rejected", self),
48-
MissingHeader(_) => serialize_json_error("original-psbt-rejected", self),
49-
InvalidContentType(_) => serialize_json_error("original-psbt-rejected", self),
50-
InvalidContentLength(_) => serialize_json_error("original-psbt-rejected", self),
51-
ContentLengthTooLarge(_) => serialize_json_error("original-psbt-rejected", self),
48+
Io(_) => serialize_json_error(OriginalPsbtRejected, self),
49+
MissingHeader(_) => serialize_json_error(OriginalPsbtRejected, self),
50+
InvalidContentType(_) => serialize_json_error(OriginalPsbtRejected, self),
51+
InvalidContentLength(_) => serialize_json_error(OriginalPsbtRejected, self),
52+
ContentLengthTooLarge(_) => serialize_json_error(OriginalPsbtRejected, self),
5253
}
5354
}
5455
}

payjoin/src/send/error.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use std::fmt::{self, Display};
2+
use std::str::FromStr;
23

34
use bitcoin::locktime::absolute::LockTime;
45
use bitcoin::transaction::Version;
56
use bitcoin::Sequence;
67

7-
use crate::error_codes::{
8-
NOT_ENOUGH_MONEY, ORIGINAL_PSBT_REJECTED, UNAVAILABLE, VERSION_UNSUPPORTED,
9-
};
8+
use crate::error_codes::ErrorCode;
109

1110
/// Error building a Sender from a SenderBuilder.
1211
///
@@ -282,8 +281,8 @@ impl ResponseError {
282281
if let Some(error_code) =
283282
json.as_object().and_then(|v| v.get("errorCode")).and_then(|v| v.as_str())
284283
{
285-
match error_code {
286-
code if code == VERSION_UNSUPPORTED => {
284+
match ErrorCode::from_str(error_code) {
285+
Ok(ErrorCode::VersionUnsupported) => {
287286
let supported = json
288287
.as_object()
289288
.and_then(|v| v.get("supported"))
@@ -292,9 +291,9 @@ impl ResponseError {
292291
.unwrap_or_default();
293292
WellKnownError::VersionUnsupported { message, supported }.into()
294293
}
295-
code if code == UNAVAILABLE => WellKnownError::Unavailable(message).into(),
296-
code if code == NOT_ENOUGH_MONEY => WellKnownError::NotEnoughMoney(message).into(),
297-
code if code == ORIGINAL_PSBT_REJECTED =>
294+
Ok(ErrorCode::Unavailable) => WellKnownError::Unavailable(message).into(),
295+
Ok(ErrorCode::NotEnoughMoney) => WellKnownError::NotEnoughMoney(message).into(),
296+
Ok(ErrorCode::OriginalPsbtRejected) =>
298297
WellKnownError::OriginalPsbtRejected(message).into(),
299298
_ => Self::Unrecognized { error_code: error_code.to_string(), message },
300299
}
@@ -373,12 +372,12 @@ pub enum WellKnownError {
373372
}
374373

375374
impl WellKnownError {
376-
pub fn error_code(&self) -> &str {
375+
pub fn error_code(&self) -> ErrorCode {
377376
match self {
378-
WellKnownError::Unavailable(_) => UNAVAILABLE,
379-
WellKnownError::NotEnoughMoney(_) => NOT_ENOUGH_MONEY,
380-
WellKnownError::VersionUnsupported { .. } => VERSION_UNSUPPORTED,
381-
WellKnownError::OriginalPsbtRejected(_) => ORIGINAL_PSBT_REJECTED,
377+
WellKnownError::Unavailable(_) => ErrorCode::Unavailable,
378+
WellKnownError::NotEnoughMoney(_) => ErrorCode::NotEnoughMoney,
379+
WellKnownError::VersionUnsupported { .. } => ErrorCode::VersionUnsupported,
380+
WellKnownError::OriginalPsbtRejected(_) => ErrorCode::OriginalPsbtRejected,
382381
}
383382
}
384383
pub fn message(&self) -> &str {
@@ -413,7 +412,7 @@ mod tests {
413412
let known_str_error = r#"{"errorCode":"version-unsupported", "message":"custom message here", "supported": [1, 2]}"#;
414413
match ResponseError::parse(known_str_error) {
415414
ResponseError::WellKnown(e) => {
416-
assert_eq!(e.error_code(), "version-unsupported");
415+
assert_eq!(e.error_code(), ErrorCode::VersionUnsupported);
417416
assert_eq!(e.message(), "custom message here");
418417
assert_eq!(
419418
e.to_string(),

0 commit comments

Comments
 (0)