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

Commit 015b970

Browse files
committed
Handle payjoin errors
1 parent 2a34e06 commit 015b970

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
@@ -674,59 +674,21 @@ impl<S: MutinyStorage> MutinyWallet<S> {
674674
return Err(MutinyError::WalletOperationFailed);
675675
};
676676

677-
let pj = {
678-
use anyhow::anyhow;
679-
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
680-
// That would reveal IP address
681-
682-
let http_client = reqwest::Client::builder().build().unwrap();
683-
684-
let ohttp_config_base64 = http_client
685-
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
686-
.send()
687-
.await
688-
.unwrap()
689-
.text()
690-
.await
691-
.unwrap();
692-
693-
let mut enroller = pj::receive::v2::Enroller::from_relay_config(
694-
crate::payjoin::PAYJOIN_DIR,
695-
&ohttp_config_base64,
696-
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
697-
);
698-
699-
// enroll client
700-
let (req, context) = enroller.extract_req().unwrap();
701-
let ohttp_response = http_client
702-
.post(req.url)
703-
.body(req.body)
704-
.send()
705-
.await
706-
.unwrap();
707-
let ohttp_response = ohttp_response.bytes().await.unwrap();
708-
let enrolled = enroller
709-
.process_res(ohttp_response.as_ref(), context)
710-
.map_err(|e| anyhow!("parse error {}", e))
711-
.unwrap();
712-
let session = self
713-
.node_manager
714-
.storage
715-
.persist_payjoin(enrolled.clone())
716-
.unwrap();
717-
let pj_uri = enrolled.fallback_target();
718-
log_debug!(self.logger, "{pj_uri}");
719-
let wallet = self.node_manager.wallet.clone();
720-
let stop = self.node_manager.stop.clone();
721-
let storage = Arc::new(self.node_manager.storage.clone());
722-
// run await payjoin task in the background as it'll keep polling the relay
723-
utils::spawn(async move {
724-
let pj_txid = NodeManager::receive_payjoin(wallet, stop, storage, session)
725-
.await
726-
.unwrap();
727-
log::info!("Received payjoin txid: {}", pj_txid);
728-
});
729-
Some(pj_uri)
677+
let pj = match self.node_manager.start_payjoin_session().await {
678+
Ok(enrolled) => {
679+
let session = self
680+
.node_manager
681+
.storage
682+
.persist_payjoin(enrolled.clone())?;
683+
let pj_uri = session.enrolled.fallback_target();
684+
log_debug!(self.logger, "{pj_uri}");
685+
self.node_manager.spawn_payjoin_receiver(session);
686+
Some(pj_uri)
687+
}
688+
Err(e) => {
689+
log_error!(self.logger, "Error enrolling payjoin: {e}");
690+
None
691+
}
730692
};
731693

732694
Ok(MutinyBip21RawMaterials {

mutiny-core/src/nodemanager.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::logging::LOGGING_KEY;
2-
use crate::payjoin::PayjoinStorage;
2+
use crate::payjoin::{Error as PayjoinError, PayjoinStorage};
33
use crate::redshift::{RedshiftManager, RedshiftStatus, RedshiftStorage};
44
use crate::storage::{MutinyStorage, DEVICE_ID_KEY, KEYCHAIN_STORE_KEY, NEED_FULL_SYNC_KEY};
55
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};
@@ -709,15 +710,7 @@ impl<S: MutinyStorage> NodeManager<S> {
709710
pub(crate) fn resume_payjoins(nm: Arc<NodeManager<S>>) {
710711
let all = nm.storage.get_payjoins().unwrap_or_default();
711712
for payjoin in all {
712-
let wallet = nm.wallet.clone();
713-
let stop = nm.stop.clone();
714-
let storage = Arc::new(nm.storage.clone());
715-
utils::spawn(async move {
716-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
717-
.await
718-
.unwrap();
719-
log::info!("Received payjoin txid: {}", pj_txid);
720-
});
713+
nm.clone().spawn_payjoin_receiver(payjoin);
721714
}
722715
}
723716

@@ -880,6 +873,31 @@ impl<S: MutinyStorage> NodeManager<S> {
880873
Err(MutinyError::WalletOperationFailed)
881874
}
882875

876+
pub async fn start_payjoin_session(&self) -> Result<Enrolled, PayjoinError> {
877+
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
878+
// That would reveal IP address
879+
880+
let http_client = reqwest::Client::builder().build()?;
881+
882+
let ohttp_config_base64 = http_client
883+
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
884+
.send()
885+
.await?
886+
.text()
887+
.await?;
888+
889+
let mut enroller = payjoin::receive::v2::Enroller::from_relay_config(
890+
crate::payjoin::PAYJOIN_DIR,
891+
&ohttp_config_base64,
892+
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
893+
);
894+
// enroll client
895+
let (req, context) = enroller.extract_req()?;
896+
let ohttp_response = http_client.post(req.url).body(req.body).send().await?;
897+
let ohttp_response = ohttp_response.bytes().await?;
898+
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
899+
}
900+
883901
// Send v1 payjoin request
884902
pub async fn send_payjoin(
885903
&self,
@@ -952,38 +970,45 @@ impl<S: MutinyStorage> NodeManager<S> {
952970
Ok(txid)
953971
}
954972

973+
pub fn spawn_payjoin_receiver(&self, session: crate::payjoin::Session) {
974+
let logger = self.logger.clone();
975+
let wallet = self.wallet.clone();
976+
let stop = self.stop.clone();
977+
let storage = Arc::new(self.storage.clone());
978+
utils::spawn(async move {
979+
match Self::receive_payjoin(wallet, stop, storage, session).await {
980+
Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"),
981+
Err(e) => log_error!(logger, "Error receiving payjoin: {e}"),
982+
};
983+
});
984+
}
985+
955986
/// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
956-
pub async fn receive_payjoin(
987+
async fn receive_payjoin(
957988
wallet: Arc<OnChainWallet<S>>,
958989
stop: Arc<AtomicBool>,
959990
storage: Arc<S>,
960991
mut session: crate::payjoin::Session,
961-
) -> Result<Txid, MutinyError> {
992+
) -> Result<Txid, PayjoinError> {
962993
let http_client = reqwest::Client::builder()
963994
//.danger_accept_invalid_certs(true) ? is tls unchecked :O
964-
.build()
965-
.unwrap();
995+
.build()?;
966996
let proposal: payjoin::receive::v2::UncheckedProposal =
967997
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session)
968998
.await
969999
.unwrap();
970-
let payjoin_proposal = wallet.process_payjoin_proposal(proposal).unwrap();
1000+
let payjoin_proposal = wallet
1001+
.process_payjoin_proposal(proposal)
1002+
.map_err(PayjoinError::Wallet)?;
9711003

972-
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req().unwrap(); // extraction failed
973-
let res = http_client
974-
.post(req.url)
975-
.body(req.body)
976-
.send()
977-
.await
978-
.unwrap();
979-
let res = res.bytes().await.unwrap();
1004+
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req()?; // extraction failed
1005+
let res = http_client.post(req.url).body(req.body).send().await?;
1006+
let res = res.bytes().await?;
9801007
// enroll must succeed
981-
let _res = payjoin_proposal
982-
.deserialize_res(res.to_vec(), ohttp_ctx)
983-
.unwrap();
1008+
let _res = payjoin_proposal.deserialize_res(res.to_vec(), ohttp_ctx)?;
9841009
// convert from bitcoin 29 to 30
9851010
let txid = payjoin_proposal.psbt().clone().extract_tx().txid();
986-
let txid = Txid::from_str(&txid.to_string()).unwrap();
1011+
let txid = Txid::from_str(&txid.to_string()).map_err(PayjoinError::Txid)?;
9871012
Ok(txid)
9881013
}
9891014

mutiny-core/src/payjoin.rs

Lines changed: 41 additions & 0 deletions
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 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)