Skip to content

Commit b46dac1

Browse files
committed
Handle v2 errors
1 parent f6efdab commit b46dac1

File tree

7 files changed

+232
-84
lines changed

7 files changed

+232
-84
lines changed

payjoin-cli/src/app.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,15 @@ impl App {
9292
&self,
9393
client: &reqwest::Client,
9494
enroll_context: &mut EnrollContext,
95-
) -> Result<UncheckedProposal, reqwest::Error> {
95+
) -> Result<UncheckedProposal> {
9696
loop {
97-
let (enroll_body, context) = enroll_context.enroll_body();
97+
let (enroll_body, context) = enroll_context.enroll_body()?;
9898
let ohttp_response =
9999
client.post(&self.config.ohttp_proxy).body(enroll_body).send().await?;
100100
let ohttp_response = ohttp_response.bytes().await?;
101-
let proposal = enroll_context.parse_proposal(ohttp_response.as_ref(), context).unwrap();
101+
let proposal = enroll_context
102+
.parse_proposal(ohttp_response.as_ref(), context)
103+
.map_err(|e| anyhow!("parse error {}", e))?;
102104
match proposal {
103105
Some(proposal) => return Ok(proposal),
104106
None => tokio::time::sleep(std::time::Duration::from_secs(5)).await,
@@ -238,8 +240,11 @@ impl App {
238240
.map_err(|e| anyhow!("Failed to process UncheckedProposal {}", e))?;
239241

240242
let receive_endpoint = format!("{}/{}", self.config.pj_endpoint, context.receive_subdir());
241-
let (body, ohttp_ctx) =
242-
payjoin_proposal.extract_v2_req(&self.config.ohttp_config, &receive_endpoint);
243+
let ohttp_config =
244+
bitcoin::base64::decode_config(&self.config.ohttp_config, base64::URL_SAFE)?;
245+
let (body, ohttp_ctx) = payjoin_proposal
246+
.extract_v2_req(&ohttp_config, &receive_endpoint)
247+
.map_err(|e| anyhow!("v2 req extraction failed {}", e))?;
243248
let res = client
244249
.post(&self.config.ohttp_proxy)
245250
.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 & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ use url::Url;
285285
use crate::input_type::InputType;
286286
use crate::optional_parameters::Params;
287287
use crate::psbt::PsbtExt;
288+
use crate::v2;
288289

289290
pub trait Headers {
290291
fn get_header(&self, key: &str) -> Option<&str>;
@@ -329,30 +330,28 @@ impl EnrollContext {
329330
format!("{}/{}", self.subdirectory(), crate::v2::RECEIVE)
330331
}
331332

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

345344
pub fn parse_proposal(
346345
&self,
347346
encrypted_proposal: &[u8],
348347
context: ohttp::ClientResponse,
349-
) -> Result<Option<UncheckedProposal>, RequestError> {
350-
let response = crate::v2::ohttp_decapsulate(context, &encrypted_proposal);
348+
) -> Result<Option<UncheckedProposal>, Error> {
349+
let response = crate::v2::ohttp_decapsulate(context, &encrypted_proposal)?;
351350
if response.is_empty() {
352351
log::debug!("response is empty");
353352
return Ok(None);
354353
}
355-
let (proposal, e) = crate::v2::decrypt_message_a(&response, self.s.secret_key());
354+
let (proposal, e) = crate::v2::decrypt_message_a(&response, self.s.secret_key())?;
356355
let mut proposal = serde_json::from_slice::<UncheckedProposal>(&proposal)
357356
.map_err(InternalRequestError::Json)?;
358357
proposal.psbt = proposal.psbt.validate().map_err(InternalRequestError::InconsistentPsbt)?;
@@ -684,20 +683,26 @@ impl PayjoinProposal {
684683
#[cfg(feature = "v2")]
685684
pub fn extract_v2_req(
686685
&self,
687-
ohttp_config: &str,
686+
ohttp_config: &Vec<u8>,
688687
receive_endpoint: &str,
689-
) -> (Vec<u8>, ohttp::ClientResponse) {
688+
) -> Result<(Vec<u8>, ohttp::ClientResponse), Error> {
690689
let e = self.v2_context.unwrap(); // TODO make v2 only
691690
let mut payjoin_bytes = self.payjoin_psbt.serialize();
692-
let body = crate::v2::encrypt_message_b(&mut payjoin_bytes, e);
693-
let ohttp_config = bitcoin::base64::decode_config(ohttp_config, base64::URL_SAFE).unwrap();
691+
let body = crate::v2::encrypt_message_b(&mut payjoin_bytes, e)?;
694692
dbg!(receive_endpoint);
695-
crate::v2::ohttp_encapsulate(&ohttp_config, "POST", receive_endpoint, Some(&body))
693+
let (req, ctx) =
694+
crate::v2::ohttp_encapsulate(&ohttp_config, "POST", receive_endpoint, Some(&body))?;
695+
Ok((req, ctx))
696696
}
697697

698-
pub fn deserialize_res(&self, res: Vec<u8>, ohttp_context: ohttp::ClientResponse) -> Vec<u8> {
698+
pub fn deserialize_res(
699+
&self,
700+
res: Vec<u8>,
701+
ohttp_context: ohttp::ClientResponse,
702+
) -> Result<Vec<u8>, Error> {
699703
// display success or failure
700-
crate::v2::ohttp_decapsulate(ohttp_context, &res)
704+
let res = crate::v2::ohttp_decapsulate(ohttp_context, &res)?;
705+
Ok(res)
701706
}
702707
}
703708

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)