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

Commit dfbbf0e

Browse files
committed
Handle payjoin errors
1 parent 570e695 commit dfbbf0e

File tree

2 files changed

+105
-74
lines changed

2 files changed

+105
-74
lines changed

Diff for: mutiny-core/src/nodemanager.rs

+64-74
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::lnurlauth::AuthManager;
22
use crate::logging::LOGGING_KEY;
3-
use crate::payjoin::PayjoinStorage;
3+
use crate::payjoin::{Error as PayjoinError, PayjoinStorage};
44
use crate::redshift::{RedshiftManager, RedshiftStatus, RedshiftStorage};
55
use crate::storage::{MutinyStorage, DEVICE_ID_KEY, KEYCHAIN_STORE_KEY, NEED_FULL_SYNC_KEY};
66
use crate::utils::{sleep, spawn};
@@ -52,6 +52,7 @@ use lnurl::lnurl::LnUrl;
5252
use lnurl::{AsyncClient as LnUrlClient, LnUrlResponse, Response};
5353
use nostr::key::XOnlyPublicKey;
5454
use nostr::{EventBuilder, Keys, Kind, Tag, TagKind};
55+
use payjoin::receive::v2::Enrolled;
5556
use payjoin::Uri;
5657
use reqwest::Client;
5758
use serde::{Deserialize, Serialize};
@@ -807,15 +808,7 @@ impl<S: MutinyStorage> NodeManager<S> {
807808
pub(crate) fn resume_payjoins(nm: Arc<NodeManager<S>>) {
808809
let all = nm.storage.get_payjoins().unwrap_or_default();
809810
for payjoin in all {
810-
let wallet = nm.wallet.clone();
811-
let stop = nm.stop.clone();
812-
let storage = Arc::new(nm.storage.clone());
813-
utils::spawn(async move {
814-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
815-
.await
816-
.unwrap();
817-
log::info!("Received payjoin txid: {}", pj_txid);
818-
});
811+
nm.clone().spawn_payjoin_receiver(payjoin);
819812
}
820813
}
821814

@@ -1021,53 +1014,18 @@ impl<S: MutinyStorage> NodeManager<S> {
10211014
return Err(MutinyError::WalletOperationFailed);
10221015
};
10231016

1024-
let pj = {
1025-
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
1026-
// That would reveal IP address
1027-
1028-
let http_client = reqwest::Client::builder().build().unwrap();
1029-
1030-
let ohttp_config_base64 = http_client
1031-
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
1032-
.send()
1033-
.await
1034-
.unwrap()
1035-
.text()
1036-
.await
1037-
.unwrap();
1038-
1039-
let mut enroller = payjoin::receive::v2::Enroller::from_relay_config(
1040-
crate::payjoin::PAYJOIN_DIR,
1041-
&ohttp_config_base64,
1042-
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
1043-
);
1044-
// enroll client
1045-
let (req, context) = enroller.extract_req().unwrap();
1046-
let ohttp_response = http_client
1047-
.post(req.url)
1048-
.body(req.body)
1049-
.send()
1050-
.await
1051-
.unwrap();
1052-
let ohttp_response = ohttp_response.bytes().await.unwrap();
1053-
let enrolled = enroller
1054-
.process_res(ohttp_response.as_ref(), context)
1055-
.map_err(|e| anyhow!("parse error {}", e))
1056-
.unwrap();
1057-
let session = self.storage.persist_payjoin(enrolled.clone()).unwrap();
1058-
let pj_uri = enrolled.fallback_target();
1059-
log_debug!(self.logger, "{pj_uri}");
1060-
// run await payjoin task in the background as it'll keep polling the relay
1061-
let wallet = self.wallet.clone();
1062-
let stop = self.stop.clone();
1063-
let storage = Arc::new(self.storage.clone());
1064-
utils::spawn(async move {
1065-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, session)
1066-
.await
1067-
.unwrap();
1068-
log::info!("Received payjoin txid: {}", pj_txid);
1069-
});
1070-
Some(pj_uri)
1017+
let pj = match self.start_payjoin_session().await {
1018+
Ok(enrolled) => {
1019+
let session = self.storage.persist_payjoin(enrolled.clone())?;
1020+
let pj_uri = session.enrolled.fallback_target();
1021+
log_debug!(self.logger, "{pj_uri}");
1022+
self.spawn_payjoin_receiver(session);
1023+
Some(pj_uri)
1024+
}
1025+
Err(e) => {
1026+
log_error!(self.logger, "Error enrolling payjoin: {e}");
1027+
None
1028+
}
10711029
};
10721030

10731031
Ok(MutinyBip21RawMaterials {
@@ -1079,6 +1037,31 @@ impl<S: MutinyStorage> NodeManager<S> {
10791037
})
10801038
}
10811039

1040+
async fn start_payjoin_session(&self) -> Result<Enrolled, PayjoinError> {
1041+
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
1042+
// That would reveal IP address
1043+
1044+
let http_client = reqwest::Client::builder().build()?;
1045+
1046+
let ohttp_config_base64 = http_client
1047+
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
1048+
.send()
1049+
.await?
1050+
.text()
1051+
.await?;
1052+
1053+
let mut enroller = payjoin::receive::v2::Enroller::from_relay_config(
1054+
crate::payjoin::PAYJOIN_DIR,
1055+
&ohttp_config_base64,
1056+
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
1057+
);
1058+
// enroll client
1059+
let (req, context) = enroller.extract_req()?;
1060+
let ohttp_response = http_client.post(req.url).body(req.body).send().await?;
1061+
let ohttp_response = ohttp_response.bytes().await?;
1062+
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
1063+
}
1064+
10821065
// Send v1 payjoin request
10831066
pub async fn send_payjoin(
10841067
&self,
@@ -1151,38 +1134,45 @@ impl<S: MutinyStorage> NodeManager<S> {
11511134
Ok(txid)
11521135
}
11531136

1137+
pub fn spawn_payjoin_receiver(&self, session: crate::payjoin::Session) {
1138+
let logger = self.logger.clone();
1139+
let wallet = self.wallet.clone();
1140+
let stop = self.stop.clone();
1141+
let storage = Arc::new(self.storage.clone());
1142+
utils::spawn(async move {
1143+
match Self::receive_payjoin(wallet, stop, storage, session).await {
1144+
Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"),
1145+
Err(e) => log_error!(logger, "Error receiving payjoin: {e}"),
1146+
};
1147+
});
1148+
}
1149+
11541150
/// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
1155-
pub async fn receive_payjoin(
1151+
async fn receive_payjoin(
11561152
wallet: Arc<OnChainWallet<S>>,
11571153
stop: Arc<AtomicBool>,
11581154
storage: Arc<S>,
11591155
mut session: crate::payjoin::Session,
1160-
) -> Result<Txid, MutinyError> {
1156+
) -> Result<Txid, PayjoinError> {
11611157
let http_client = reqwest::Client::builder()
11621158
//.danger_accept_invalid_certs(true) ? is tls unchecked :O
1163-
.build()
1164-
.unwrap();
1159+
.build()?;
11651160
let proposal: payjoin::receive::v2::UncheckedProposal =
11661161
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session)
11671162
.await
11681163
.unwrap();
1169-
let payjoin_proposal = wallet.process_payjoin_proposal(proposal).unwrap();
1164+
let payjoin_proposal = wallet
1165+
.process_payjoin_proposal(proposal)
1166+
.map_err(PayjoinError::Wallet)?;
11701167

1171-
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req().unwrap(); // extraction failed
1172-
let res = http_client
1173-
.post(req.url)
1174-
.body(req.body)
1175-
.send()
1176-
.await
1177-
.unwrap();
1178-
let res = res.bytes().await.unwrap();
1168+
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req()?; // extraction failed
1169+
let res = http_client.post(req.url).body(req.body).send().await?;
1170+
let res = res.bytes().await?;
11791171
// enroll must succeed
1180-
let _res = payjoin_proposal
1181-
.deserialize_res(res.to_vec(), ohttp_ctx)
1182-
.unwrap();
1172+
let _res = payjoin_proposal.deserialize_res(res.to_vec(), ohttp_ctx)?;
11831173
// convert from bitcoin 29 to 30
11841174
let txid = payjoin_proposal.psbt().clone().extract_tx().txid();
1185-
let txid = Txid::from_str(&txid.to_string()).unwrap();
1175+
let txid = Txid::from_str(&txid.to_string()).map_err(PayjoinError::Txid)?;
11861176
Ok(txid)
11871177
}
11881178

Diff for: mutiny-core/src/payjoin.rs

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

0 commit comments

Comments
 (0)