Skip to content

Commit 38f7dc1

Browse files
committed
Handle v2 errors (v1 fails)
1 parent a2b1f46 commit 38f7dc1

File tree

7 files changed

+232
-87
lines changed

7 files changed

+232
-87
lines changed

payjoin-cli/src/app.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,14 @@ impl App {
9191
&self,
9292
client: &reqwest::blocking::Client,
9393
enroll_context: &mut EnrollContext,
94-
) -> Result<UncheckedProposal, reqwest::Error> {
94+
) -> Result<UncheckedProposal> {
9595
loop {
96-
let (enroll_body, context) = enroll_context.enroll_body();
96+
let (enroll_body, context) = enroll_context.enroll_body()?;
9797
let ohttp_response = client.post(&self.config.ohttp_proxy).body(enroll_body).send()?;
9898
let ohttp_response = ohttp_response.bytes()?;
99-
let proposal =
100-
enroll_context.parse_relay_response(ohttp_response.as_ref(), context).unwrap();
99+
let proposal = enroll_context
100+
.parse_relay_response(ohttp_response.as_ref(), context)
101+
.map_err(|e| anyhow!("parse error {}", e))?;
101102
match proposal {
102103
Some(proposal) => return Ok(proposal),
103104
None => std::thread::sleep(std::time::Duration::from_secs(5)),
@@ -235,8 +236,11 @@ impl App {
235236
.map_err(|e| anyhow!("Failed to parse into UncheckedProposal {}", e))?;
236237

237238
let receive_endpoint = format!("{}/{}", self.config.pj_endpoint, context.receive_subdir());
238-
let (body, ohttp_ctx) =
239-
payjoin_proposal.extract_v2_req(&self.config.ohttp_config, &receive_endpoint);
239+
let ohttp_config =
240+
bitcoin::base64::decode_config(&self.config.ohttp_config, base64::URL_SAFE)?;
241+
let (body, ohttp_ctx) = payjoin_proposal
242+
.extract_v2_req(&ohttp_config, &receive_endpoint)
243+
.map_err(|e| anyhow!("v2 req extraction failed {}", e))?;
240244
let res = client
241245
.post(&self.config.ohttp_proxy)
242246
.body(body)

payjoin-relay/src/main.rs

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ fn init_ohttp() -> Result<ohttp::Server> {
6868
&[SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305)];
6969

7070
// create or read from file
71-
let server_config = ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap();
72-
let encoded_config = server_config.encode().unwrap();
71+
let server_config = ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC))?;
72+
let encoded_config = server_config.encode()?;
7373
let b64_config = payjoin::bitcoin::base64::encode_config(
7474
&encoded_config,
7575
payjoin::bitcoin::base64::Config::new(
@@ -83,43 +83,53 @@ fn init_ohttp() -> Result<ohttp::Server> {
8383

8484
async fn handle_ohttp(
8585
enc_request: Bytes,
86-
mut target: Router,
86+
target: Router,
8787
ohttp: Arc<ohttp::Server>,
8888
) -> (StatusCode, Vec<u8>) {
89+
match handle_ohttp_inner(enc_request, target, ohttp).await {
90+
Ok(res) => res,
91+
Err(e) => {
92+
tracing::error!("ohttp error: {:?}", e);
93+
(StatusCode::INTERNAL_SERVER_ERROR, vec![])
94+
}
95+
}
96+
}
97+
98+
async fn handle_ohttp_inner(
99+
enc_request: Bytes,
100+
mut target: Router,
101+
ohttp: Arc<ohttp::Server>,
102+
) -> Result<(StatusCode, Vec<u8>)> {
89103
use axum::body::Body;
90104
use http::Uri;
91105
use tower_service::Service;
92106

93-
// decapsulate
94-
let (bhttp_req, res_ctx) = ohttp.decapsulate(&enc_request).unwrap();
107+
let (bhttp_req, res_ctx) = ohttp.decapsulate(&enc_request)?;
95108
let mut cursor = std::io::Cursor::new(bhttp_req);
96-
let req = bhttp::Message::read_bhttp(&mut cursor).unwrap();
97-
// let parsed_request: httparse::Request = httparse::Request::new(&mut vec![]).parse(cursor).unwrap();
98-
// // handle request
99-
// Request::new
109+
let req = bhttp::Message::read_bhttp(&mut cursor)?;
100110
let uri = Uri::builder()
101-
.scheme(req.control().scheme().unwrap())
102-
.authority(req.control().authority().unwrap())
103-
.path_and_query(req.control().path().unwrap())
104-
.build()
105-
.unwrap();
111+
.scheme(req.control().scheme().unwrap_or_default())
112+
.authority(req.control().authority().unwrap_or_default())
113+
.path_and_query(req.control().path().unwrap_or_default())
114+
.build()?;
106115
let body = req.content().to_vec();
107-
let mut request = Request::builder().uri(uri).method(req.control().method().unwrap());
116+
let mut request =
117+
Request::builder().uri(uri).method(req.control().method().unwrap_or_default());
108118
for header in req.header().fields() {
109119
request = request.header(header.name(), header.value())
110120
}
111-
let request = request.body(Body::from(body)).unwrap();
121+
let request = request.body(Body::from(body))?;
112122

113-
let response = target.call(request).await.unwrap();
123+
let response = target.call(request).await?;
114124

115125
let (parts, body) = response.into_parts();
116126
let mut bhttp_res = bhttp::Message::response(parts.status.as_u16());
117-
let full_body = hyper::body::to_bytes(body).await.unwrap();
127+
let full_body = hyper::body::to_bytes(body).await?;
118128
bhttp_res.write_content(&full_body);
119129
let mut bhttp_bytes = Vec::new();
120-
bhttp_res.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_bytes).unwrap();
121-
let ohttp_res = res_ctx.encapsulate(&bhttp_bytes).unwrap();
122-
(StatusCode::OK, ohttp_res)
130+
bhttp_res.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_bytes)?;
131+
let ohttp_res = res_ctx.encapsulate(&bhttp_bytes)?;
132+
Ok((StatusCode::OK, ohttp_res))
123133
}
124134

125135
fn ohttp_config(server: &ohttp::Server) -> Result<String> {

payjoin/src/receive/error.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ pub enum Error {
77
BadRequest(RequestError),
88
// To be returned as HTTP 500
99
Server(Box<dyn error::Error>),
10+
// V2 d/encapsulation failed
11+
#[cfg(feature = "v2")]
12+
V2(crate::v2::Error),
1013
}
1114

1215
impl fmt::Display for Error {
1316
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1417
match &self {
1518
Self::BadRequest(e) => e.fmt(f),
1619
Self::Server(e) => write!(f, "Internal Server Error: {}", e),
20+
#[cfg(feature = "v2")]
21+
Self::V2(e) => e.fmt(f),
1722
}
1823
}
1924
}
@@ -23,6 +28,8 @@ impl error::Error for Error {
2328
match &self {
2429
Self::BadRequest(_) => None,
2530
Self::Server(e) => Some(e.as_ref()),
31+
#[cfg(feature = "v2")]
32+
Self::V2(e) => Some(e),
2633
}
2734
}
2835
}
@@ -31,6 +38,15 @@ impl From<RequestError> for Error {
3138
fn from(e: RequestError) -> Self { Error::BadRequest(e) }
3239
}
3340

41+
impl From<InternalRequestError> for Error {
42+
fn from(e: InternalRequestError) -> Self { Error::BadRequest(e.into()) }
43+
}
44+
45+
impl From<crate::v2::Error> for Error {
46+
#[cfg(feature = "v2")]
47+
fn from(e: crate::v2::Error) -> Self { Error::V2(e) }
48+
}
49+
3450
/// Error that may occur when the request from sender is malformed.
3551
///
3652
/// This is currently opaque type because we aren't sure which variants will stay.

payjoin/src/receive/mod.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ use url::Url;
283283
use crate::input_type::InputType;
284284
use crate::optional_parameters::Params;
285285
use crate::psbt::PsbtExt;
286+
use crate::v2;
286287

287288
pub trait Headers {
288289
fn get_header(&self, key: &str) -> Option<&str>;
@@ -328,32 +329,30 @@ impl EnrollContext {
328329
format!("{}/{}", self.subdirectory(), crate::v2::RECEIVE)
329330
}
330331

331-
pub fn enroll_body(&mut self) -> (Vec<u8>, ohttp::ClientResponse) {
332+
pub fn enroll_body(&mut self) -> Result<(Vec<u8>, ohttp::ClientResponse), crate::v2::Error> {
332333
let receive_endpoint = self.receive_subdir();
333334
log::debug!("{}{}", self.relay_url.as_str(), receive_endpoint);
334-
let (ohttp_req, ctx) = crate::v2::ohttp_encapsulate(
335+
crate::v2::ohttp_encapsulate(
335336
&self.ohttp_config,
336337
"GET",
337338
format!("{}{}", self.relay_url.as_str(), receive_endpoint).as_str(),
338339
None,
339-
);
340-
341-
(ohttp_req, ctx)
340+
)
342341
}
343342

344343
pub fn parse_relay_response(
345344
&self,
346345
mut body: impl std::io::Read,
347346
context: ohttp::ClientResponse,
348-
) -> Result<Option<UncheckedProposal>, RequestError> {
347+
) -> Result<Option<UncheckedProposal>, Error> {
349348
let mut buf = Vec::new();
350349
let _ = body.read_to_end(&mut buf);
351-
let response = crate::v2::ohttp_decapsulate(context, &buf);
350+
let response = crate::v2::ohttp_decapsulate(context, &buf)?;
352351
if response.is_empty() {
353352
log::debug!("response is empty");
354353
return Ok(None);
355354
}
356-
let (proposal, e) = crate::v2::decrypt_message_a(&response, self.s.secret_key());
355+
let (proposal, e) = crate::v2::decrypt_message_a(&response, self.s.secret_key())?;
357356
let mut proposal = serde_json::from_slice::<UncheckedProposal>(&proposal)
358357
.map_err(InternalRequestError::Json)?;
359358
proposal.psbt = proposal.psbt.validate().map_err(InternalRequestError::InconsistentPsbt)?;
@@ -935,21 +934,26 @@ impl PayjoinProposal {
935934
#[cfg(feature = "v2")]
936935
pub fn extract_v2_req(
937936
&self,
938-
ohttp_config: &str,
937+
ohttp_config: &Vec<u8>,
939938
receive_endpoint: &str,
940-
) -> (Vec<u8>, ohttp::ClientResponse) {
939+
) -> Result<(Vec<u8>, ohttp::ClientResponse), Error> {
941940
let e = self.v2_context.unwrap(); // TODO make v2 only
942941
let mut payjoin_bytes = self.payjoin_psbt.serialize();
943-
let body = crate::v2::encrypt_message_b(&mut payjoin_bytes, e);
944-
let ohttp_config = bitcoin::base64::decode_config(ohttp_config, base64::URL_SAFE).unwrap();
942+
let body = crate::v2::encrypt_message_b(&mut payjoin_bytes, e)?;
945943
dbg!(receive_endpoint);
946-
crate::v2::ohttp_encapsulate(&ohttp_config, "POST", receive_endpoint, Some(&body))
944+
let (req, ctx) =
945+
crate::v2::ohttp_encapsulate(&ohttp_config, "POST", receive_endpoint, Some(&body))?;
946+
Ok((req, ctx))
947947
}
948948

949-
#[cfg(feature = "v2")]
950-
pub fn deserialize_res(&self, res: Vec<u8>, ohttp_context: ohttp::ClientResponse) -> Vec<u8> {
949+
pub fn deserialize_res(
950+
&self,
951+
res: Vec<u8>,
952+
ohttp_context: ohttp::ClientResponse,
953+
) -> Result<Vec<u8>, Error> {
951954
// display success or failure
952-
crate::v2::ohttp_decapsulate(ohttp_context, &res)
955+
let res = crate::v2::ohttp_decapsulate(ohttp_context, &res)?;
956+
Ok(res)
953957
}
954958
}
955959

payjoin/src/send/error.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ pub struct ValidationError {
1616

1717
#[derive(Debug)]
1818
pub(crate) enum InternalValidationError {
19-
Psbt(bitcoin::psbt::PsbtParseError),
19+
PsbtParse(bitcoin::psbt::PsbtParseError),
2020
Io(std::io::Error),
2121
InvalidInputType(InputTypeError),
2222
InvalidProposedInput(crate::psbt::PrevTxOutError),
23-
VersionsDontMatch { proposed: i32, original: i32 },
24-
LockTimesDontMatch { proposed: LockTime, original: LockTime },
25-
SenderTxinSequenceChanged { proposed: Sequence, original: Sequence },
23+
VersionsDontMatch {
24+
proposed: i32,
25+
original: i32,
26+
},
27+
LockTimesDontMatch {
28+
proposed: LockTime,
29+
original: LockTime,
30+
},
31+
SenderTxinSequenceChanged {
32+
proposed: Sequence,
33+
original: Sequence,
34+
},
2635
SenderTxinContainsNonWitnessUtxo,
2736
SenderTxinContainsWitnessUtxo,
2837
SenderTxinContainsFinalScriptSig,
@@ -32,7 +41,10 @@ pub(crate) enum InternalValidationError {
3241
ReceiverTxinNotFinalized,
3342
ReceiverTxinMissingUtxoInfo,
3443
MixedSequence,
35-
MixedInputTypes { proposed: InputType, original: InputType },
44+
MixedInputTypes {
45+
proposed: InputType,
46+
original: InputType,
47+
},
3648
MissingOrShuffledInputs,
3749
TxOutContainsKeyPaths,
3850
FeeContributionExceedsMaximum,
@@ -44,6 +56,10 @@ pub(crate) enum InternalValidationError {
4456
PayeeTookContributedFee,
4557
FeeContributionPaysOutputSizeIncrease,
4658
FeeRateBelowMinimum,
59+
#[cfg(feature = "v2")]
60+
V2(crate::v2::Error),
61+
#[cfg(feature = "v2")]
62+
Psbt(bitcoin::psbt::Error),
4763
}
4864

4965
impl From<InternalValidationError> for ValidationError {
@@ -58,7 +74,7 @@ impl fmt::Display for ValidationError {
5874
use InternalValidationError::*;
5975

6076
match &self.internal {
61-
Psbt(e) => write!(f, "couldn't decode PSBT: {}", e),
77+
PsbtParse(e) => write!(f, "couldn't decode PSBT: {}", e),
6278
Io(e) => write!(f, "couldn't read PSBT: {}", e),
6379
InvalidInputType(e) => write!(f, "invalid transaction input type: {}", e),
6480
InvalidProposedInput(e) => write!(f, "invalid proposed transaction input: {}", e),
@@ -86,6 +102,10 @@ impl fmt::Display for ValidationError {
86102
PayeeTookContributedFee => write!(f, "payee tried to take fee contribution for himself"),
87103
FeeContributionPaysOutputSizeIncrease => write!(f, "fee contribution pays for additional outputs"),
88104
FeeRateBelowMinimum => write!(f, "the fee rate of proposed transaction is below minimum"),
105+
#[cfg(feature = "v2")]
106+
V2(e) => write!(f, "v2 error: {}", e),
107+
#[cfg(feature = "v2")]
108+
Psbt(e) => write!(f, "psbt error: {}", e),
89109
}
90110
}
91111
}
@@ -95,7 +115,7 @@ impl std::error::Error for ValidationError {
95115
use InternalValidationError::*;
96116

97117
match &self.internal {
98-
Psbt(error) => Some(error),
118+
PsbtParse(error) => Some(error),
99119
Io(error) => Some(error),
100120
InvalidInputType(error) => Some(error),
101121
InvalidProposedInput(error) => Some(error),
@@ -123,6 +143,10 @@ impl std::error::Error for ValidationError {
123143
PayeeTookContributedFee => None,
124144
FeeContributionPaysOutputSizeIncrease => None,
125145
FeeRateBelowMinimum => None,
146+
#[cfg(feature = "v2")]
147+
V2(error) => Some(error),
148+
#[cfg(feature = "v2")]
149+
Psbt(error) => Some(error),
126150
}
127151
}
128152
}
@@ -152,6 +176,8 @@ pub(crate) enum InternalCreateRequestError {
152176
UriDoesNotSupportPayjoin,
153177
PrevTxOut(crate::psbt::PrevTxOutError),
154178
InputType(crate::input_type::InputTypeError),
179+
#[cfg(feature = "v2")]
180+
V2(crate::v2::Error),
155181
}
156182

157183
impl fmt::Display for CreateRequestError {
@@ -174,6 +200,8 @@ impl fmt::Display for CreateRequestError {
174200
UriDoesNotSupportPayjoin => write!(f, "the URI does not support payjoin"),
175201
PrevTxOut(e) => write!(f, "invalid previous transaction output: {}", e),
176202
InputType(e) => write!(f, "invalid input type: {}", e),
203+
#[cfg(feature = "v2")]
204+
V2(e) => write!(f, "v2 error: {}", e),
177205
}
178206
}
179207
}
@@ -198,6 +226,8 @@ impl std::error::Error for CreateRequestError {
198226
UriDoesNotSupportPayjoin => None,
199227
PrevTxOut(error) => Some(error),
200228
InputType(error) => Some(error),
229+
#[cfg(feature = "v2")]
230+
V2(error) => Some(error),
201231
}
202232
}
203233
}

0 commit comments

Comments
 (0)