Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit f17c3f8

Browse files
committed
Handle payjoin errors
1 parent 6bed5bf commit f17c3f8

File tree

3 files changed

+108
-80
lines changed

3 files changed

+108
-80
lines changed

mutiny-core/src/lib.rs

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,59 +1056,21 @@ impl<S: MutinyStorage> MutinyWallet<S> {
10561056
return Err(MutinyError::WalletOperationFailed);
10571057
};
10581058

1059-
let pj = {
1060-
use anyhow::anyhow;
1061-
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
1062-
// That would reveal IP address
1063-
1064-
let http_client = reqwest::Client::builder().build().unwrap();
1065-
1066-
let ohttp_config_base64 = http_client
1067-
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
1068-
.send()
1069-
.await
1070-
.unwrap()
1071-
.text()
1072-
.await
1073-
.unwrap();
1074-
1075-
let mut enroller = pj::receive::v2::Enroller::from_relay_config(
1076-
crate::payjoin::PAYJOIN_DIR,
1077-
&ohttp_config_base64,
1078-
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
1079-
);
1080-
1081-
// enroll client
1082-
let (req, context) = enroller.extract_req().unwrap();
1083-
let ohttp_response = http_client
1084-
.post(req.url)
1085-
.body(req.body)
1086-
.send()
1087-
.await
1088-
.unwrap();
1089-
let ohttp_response = ohttp_response.bytes().await.unwrap();
1090-
let enrolled = enroller
1091-
.process_res(ohttp_response.as_ref(), context)
1092-
.map_err(|e| anyhow!("parse error {}", e))
1093-
.unwrap();
1094-
let session = self
1095-
.node_manager
1096-
.storage
1097-
.persist_payjoin(enrolled.clone())
1098-
.unwrap();
1099-
let pj_uri = enrolled.fallback_target();
1100-
log_debug!(self.logger, "{pj_uri}");
1101-
let wallet = self.node_manager.wallet.clone();
1102-
let stop = self.node_manager.stop.clone();
1103-
let storage = Arc::new(self.node_manager.storage.clone());
1104-
// run await payjoin task in the background as it'll keep polling the relay
1105-
utils::spawn(async move {
1106-
let pj_txid = NodeManager::receive_payjoin(wallet, stop, storage, session)
1107-
.await
1108-
.unwrap();
1109-
log::info!("Received payjoin txid: {}", pj_txid);
1110-
});
1111-
Some(pj_uri)
1059+
let pj = match self.node_manager.start_payjoin_session().await {
1060+
Ok(enrolled) => {
1061+
let session = self
1062+
.node_manager
1063+
.storage
1064+
.persist_payjoin(enrolled.clone())?;
1065+
let pj_uri = session.enrolled.fallback_target();
1066+
log_debug!(self.logger, "{pj_uri}");
1067+
self.node_manager.spawn_payjoin_receiver(session);
1068+
Some(pj_uri)
1069+
}
1070+
Err(e) => {
1071+
log_error!(self.logger, "Error enrolling payjoin: {e}");
1072+
None
1073+
}
11121074
};
11131075

11141076
Ok(MutinyBip21RawMaterials {

mutiny-core/src/nodemanager.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::event::HTLCStatus;
22
use crate::labels::LabelStorage;
33
use crate::logging::LOGGING_KEY;
4-
use crate::payjoin::PayjoinStorage;
4+
use crate::payjoin::{Error as PayjoinError, PayjoinStorage};
55
use crate::utils::{sleep, spawn};
66
use crate::ActivityItem;
77
use crate::MutinyInvoice;
@@ -48,6 +48,7 @@ use lightning::util::logger::*;
4848
use lightning::{log_debug, log_error, log_info, log_warn};
4949
use lightning_invoice::Bolt11Invoice;
5050
use lightning_transaction_sync::EsploraSyncClient;
51+
use payjoin::receive::v2::Enrolled;
5152
use payjoin::Uri;
5253
use reqwest::Client;
5354
use serde::{Deserialize, Serialize};
@@ -580,15 +581,7 @@ impl<S: MutinyStorage> NodeManager<S> {
580581
pub(crate) fn resume_payjoins(nm: Arc<NodeManager<S>>) {
581582
let all = nm.storage.get_payjoins().unwrap_or_default();
582583
for payjoin in all {
583-
let wallet = nm.wallet.clone();
584-
let stop = nm.stop.clone();
585-
let storage = Arc::new(nm.storage.clone());
586-
utils::spawn(async move {
587-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
588-
.await
589-
.unwrap();
590-
log::info!("Received payjoin txid: {}", pj_txid);
591-
});
584+
nm.clone().spawn_payjoin_receiver(payjoin);
592585
}
593586
}
594587

@@ -680,6 +673,31 @@ impl<S: MutinyStorage> NodeManager<S> {
680673
Err(MutinyError::WalletOperationFailed)
681674
}
682675

676+
pub async fn start_payjoin_session(&self) -> Result<Enrolled, PayjoinError> {
677+
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
678+
// That would reveal IP address
679+
680+
let http_client = reqwest::Client::builder().build()?;
681+
682+
let ohttp_config_base64 = http_client
683+
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
684+
.send()
685+
.await?
686+
.text()
687+
.await?;
688+
689+
let mut enroller = payjoin::receive::v2::Enroller::from_relay_config(
690+
crate::payjoin::PAYJOIN_DIR,
691+
&ohttp_config_base64,
692+
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
693+
);
694+
// enroll client
695+
let (req, context) = enroller.extract_req()?;
696+
let ohttp_response = http_client.post(req.url).body(req.body).send().await?;
697+
let ohttp_response = ohttp_response.bytes().await?;
698+
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
699+
}
700+
683701
// Send v1 payjoin request
684702
pub async fn send_payjoin(
685703
&self,
@@ -753,38 +771,45 @@ impl<S: MutinyStorage> NodeManager<S> {
753771
Ok(txid)
754772
}
755773

774+
pub fn spawn_payjoin_receiver(&self, session: crate::payjoin::Session) {
775+
let logger = self.logger.clone();
776+
let wallet = self.wallet.clone();
777+
let stop = self.stop.clone();
778+
let storage = Arc::new(self.storage.clone());
779+
utils::spawn(async move {
780+
match Self::receive_payjoin(wallet, stop, storage, session).await {
781+
Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"),
782+
Err(e) => log_error!(logger, "Error receiving payjoin: {e}"),
783+
};
784+
});
785+
}
786+
756787
/// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
757-
pub async fn receive_payjoin(
788+
async fn receive_payjoin(
758789
wallet: Arc<OnChainWallet<S>>,
759790
stop: Arc<AtomicBool>,
760791
storage: Arc<S>,
761792
mut session: crate::payjoin::Session,
762-
) -> Result<Txid, MutinyError> {
793+
) -> Result<Txid, PayjoinError> {
763794
let http_client = reqwest::Client::builder()
764795
//.danger_accept_invalid_certs(true) ? is tls unchecked :O
765-
.build()
766-
.unwrap();
796+
.build()?;
767797
let proposal: payjoin::receive::v2::UncheckedProposal =
768798
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session)
769799
.await
770800
.unwrap();
771-
let payjoin_proposal = wallet.process_payjoin_proposal(proposal).unwrap();
801+
let payjoin_proposal = wallet
802+
.process_payjoin_proposal(proposal)
803+
.map_err(PayjoinError::Wallet)?;
772804

773-
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req().unwrap(); // extraction failed
774-
let res = http_client
775-
.post(req.url)
776-
.body(req.body)
777-
.send()
778-
.await
779-
.unwrap();
780-
let res = res.bytes().await.unwrap();
805+
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req()?; // extraction failed
806+
let res = http_client.post(req.url).body(req.body).send().await?;
807+
let res = res.bytes().await?;
781808
// enroll must succeed
782-
let _res = payjoin_proposal
783-
.deserialize_res(res.to_vec(), ohttp_ctx)
784-
.unwrap();
809+
let _res = payjoin_proposal.deserialize_res(res.to_vec(), ohttp_ctx)?;
785810
// convert from bitcoin 29 to 30
786811
let txid = payjoin_proposal.psbt().clone().extract_tx().txid();
787-
let txid = Txid::from_str(&txid.to_string()).unwrap();
812+
let txid = Txid::from_str(&txid.to_string()).map_err(PayjoinError::Txid)?;
788813
Ok(txid)
789814
}
790815

mutiny-core/src/payjoin.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,44 @@ impl<S: MutinyStorage> PayjoinStorage for S {
6161
self.delete(&[get_payjoin_key(id)])
6262
}
6363
}
64+
65+
#[derive(Debug)]
66+
pub enum Error {
67+
Reqwest(reqwest::Error),
68+
ReceiverStateMachine(payjoin::receive::Error),
69+
V2Encapsulation(payjoin::v2::Error),
70+
Wallet(payjoin::Error),
71+
Txid(bitcoin::hashes::hex::Error),
72+
}
73+
74+
impl std::error::Error for Error {}
75+
76+
impl std::fmt::Display for Error {
77+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
78+
match &self {
79+
Error::Reqwest(e) => write!(f, "Reqwest error: {}", e),
80+
Error::ReceiverStateMachine(e) => write!(f, "Payjoin error: {}", e),
81+
Error::V2Encapsulation(e) => write!(f, "Payjoin v2 error: {}", e),
82+
Error::Wallet(e) => write!(f, "Payjoin wallet error: {}", e),
83+
Error::Txid(e) => write!(f, "Payjoin txid error: {}", e),
84+
}
85+
}
86+
}
87+
88+
impl From<reqwest::Error> for Error {
89+
fn from(e: reqwest::Error) -> Self {
90+
Error::Reqwest(e)
91+
}
92+
}
93+
94+
impl From<payjoin::receive::Error> for Error {
95+
fn from(e: payjoin::receive::Error) -> Self {
96+
Error::ReceiverStateMachine(e)
97+
}
98+
}
99+
100+
impl From<payjoin::v2::Error> for Error {
101+
fn from(e: payjoin::v2::Error) -> Self {
102+
Error::V2Encapsulation(e)
103+
}
104+
}

0 commit comments

Comments
 (0)