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

Commit 159257c

Browse files
committed
Handle payjoin errors
1 parent 19d173b commit 159257c

3 files changed

Lines changed: 108 additions & 80 deletions

File tree

mutiny-core/src/lib.rs

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

912-
let pj = {
913-
use anyhow::anyhow;
914-
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
915-
// That would reveal IP address
916-
917-
let http_client = reqwest::Client::builder().build().unwrap();
918-
919-
let ohttp_config_base64 = http_client
920-
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
921-
.send()
922-
.await
923-
.unwrap()
924-
.text()
925-
.await
926-
.unwrap();
927-
928-
let mut enroller = pj::receive::v2::Enroller::from_relay_config(
929-
crate::payjoin::PAYJOIN_DIR,
930-
&ohttp_config_base64,
931-
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
932-
);
933-
934-
// enroll client
935-
let (req, context) = enroller.extract_req().unwrap();
936-
let ohttp_response = http_client
937-
.post(req.url)
938-
.body(req.body)
939-
.send()
940-
.await
941-
.unwrap();
942-
let ohttp_response = ohttp_response.bytes().await.unwrap();
943-
let enrolled = enroller
944-
.process_res(ohttp_response.as_ref(), context)
945-
.map_err(|e| anyhow!("parse error {}", e))
946-
.unwrap();
947-
let session = self
948-
.node_manager
949-
.storage
950-
.persist_payjoin(enrolled.clone())
951-
.unwrap();
952-
let pj_uri = enrolled.fallback_target();
953-
log_debug!(self.logger, "{pj_uri}");
954-
let wallet = self.node_manager.wallet.clone();
955-
let stop = self.node_manager.stop.clone();
956-
let storage = Arc::new(self.node_manager.storage.clone());
957-
// run await payjoin task in the background as it'll keep polling the relay
958-
utils::spawn(async move {
959-
let pj_txid = NodeManager::receive_payjoin(wallet, stop, storage, session)
960-
.await
961-
.unwrap();
962-
log::info!("Received payjoin txid: {}", pj_txid);
963-
});
964-
Some(pj_uri)
912+
let pj = match self.node_manager.start_payjoin_session().await {
913+
Ok(enrolled) => {
914+
let session = self
915+
.node_manager
916+
.storage
917+
.persist_payjoin(enrolled.clone())?;
918+
let pj_uri = session.enrolled.fallback_target();
919+
log_debug!(self.logger, "{pj_uri}");
920+
self.node_manager.spawn_payjoin_receiver(session);
921+
Some(pj_uri)
922+
}
923+
Err(e) => {
924+
log_error!(self.logger, "Error enrolling payjoin: {e}");
925+
None
926+
}
965927
};
966928

967929
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, PaymentInfo};
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::MutinyWalletConfig;
@@ -47,6 +47,7 @@ use lightning::util::logger::*;
4747
use lightning::{log_debug, log_error, log_info, log_warn};
4848
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
4949
use lightning_transaction_sync::EsploraSyncClient;
50+
use payjoin::receive::v2::Enrolled;
5051
use payjoin::Uri;
5152
use reqwest::Client;
5253
use serde::{Deserialize, Serialize};
@@ -692,15 +693,7 @@ impl<S: MutinyStorage> NodeManager<S> {
692693
pub(crate) fn resume_payjoins(nm: Arc<NodeManager<S>>) {
693694
let all = nm.storage.get_payjoins().unwrap_or_default();
694695
for payjoin in all {
695-
let wallet = nm.wallet.clone();
696-
let stop = nm.stop.clone();
697-
let storage = Arc::new(nm.storage.clone());
698-
utils::spawn(async move {
699-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
700-
.await
701-
.unwrap();
702-
log::info!("Received payjoin txid: {}", pj_txid);
703-
});
696+
nm.clone().spawn_payjoin_receiver(payjoin);
704697
}
705698
}
706699

@@ -792,6 +785,31 @@ impl<S: MutinyStorage> NodeManager<S> {
792785
Err(MutinyError::WalletOperationFailed)
793786
}
794787

788+
pub async fn start_payjoin_session(&self) -> Result<Enrolled, PayjoinError> {
789+
// DANGER! TODO get from &self config, do not get config directly from PAYJOIN_DIR ohttp-gateway
790+
// That would reveal IP address
791+
792+
let http_client = reqwest::Client::builder().build()?;
793+
794+
let ohttp_config_base64 = http_client
795+
.get(format!("{}/ohttp-config", crate::payjoin::PAYJOIN_DIR))
796+
.send()
797+
.await?
798+
.text()
799+
.await?;
800+
801+
let mut enroller = payjoin::receive::v2::Enroller::from_relay_config(
802+
crate::payjoin::PAYJOIN_DIR,
803+
&ohttp_config_base64,
804+
crate::payjoin::OHTTP_RELAYS[0], // TODO pick ohttp relay at random
805+
);
806+
// enroll client
807+
let (req, context) = enroller.extract_req()?;
808+
let ohttp_response = http_client.post(req.url).body(req.body).send().await?;
809+
let ohttp_response = ohttp_response.bytes().await?;
810+
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
811+
}
812+
795813
// Send v1 payjoin request
796814
pub async fn send_payjoin(
797815
&self,
@@ -864,38 +882,45 @@ impl<S: MutinyStorage> NodeManager<S> {
864882
Ok(txid)
865883
}
866884

885+
pub fn spawn_payjoin_receiver(&self, session: crate::payjoin::Session) {
886+
let logger = self.logger.clone();
887+
let wallet = self.wallet.clone();
888+
let stop = self.stop.clone();
889+
let storage = Arc::new(self.storage.clone());
890+
utils::spawn(async move {
891+
match Self::receive_payjoin(wallet, stop, storage, session).await {
892+
Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"),
893+
Err(e) => log_error!(logger, "Error receiving payjoin: {e}"),
894+
};
895+
});
896+
}
897+
867898
/// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
868-
pub async fn receive_payjoin(
899+
async fn receive_payjoin(
869900
wallet: Arc<OnChainWallet<S>>,
870901
stop: Arc<AtomicBool>,
871902
storage: Arc<S>,
872903
mut session: crate::payjoin::Session,
873-
) -> Result<Txid, MutinyError> {
904+
) -> Result<Txid, PayjoinError> {
874905
let http_client = reqwest::Client::builder()
875906
//.danger_accept_invalid_certs(true) ? is tls unchecked :O
876-
.build()
877-
.unwrap();
907+
.build()?;
878908
let proposal: payjoin::receive::v2::UncheckedProposal =
879909
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session)
880910
.await
881911
.unwrap();
882-
let payjoin_proposal = wallet.process_payjoin_proposal(proposal).unwrap();
912+
let payjoin_proposal = wallet
913+
.process_payjoin_proposal(proposal)
914+
.map_err(PayjoinError::Wallet)?;
883915

884-
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req().unwrap(); // extraction failed
885-
let res = http_client
886-
.post(req.url)
887-
.body(req.body)
888-
.send()
889-
.await
890-
.unwrap();
891-
let res = res.bytes().await.unwrap();
916+
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req()?; // extraction failed
917+
let res = http_client.post(req.url).body(req.body).send().await?;
918+
let res = res.bytes().await?;
892919
// enroll must succeed
893-
let _res = payjoin_proposal
894-
.deserialize_res(res.to_vec(), ohttp_ctx)
895-
.unwrap();
920+
let _res = payjoin_proposal.deserialize_res(res.to_vec(), ohttp_ctx)?;
896921
// convert from bitcoin 29 to 30
897922
let txid = payjoin_proposal.psbt().clone().extract_tx().txid();
898-
let txid = Txid::from_str(&txid.to_string()).unwrap();
923+
let txid = Txid::from_str(&txid.to_string()).map_err(PayjoinError::Txid)?;
899924
Ok(txid)
900925
}
901926

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)