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

Commit c409ad6

Browse files
committed
Handle payjoin errors
1 parent ee78dab commit c409ad6

File tree

3 files changed

+76
-76
lines changed

3 files changed

+76
-76
lines changed

mutiny-core/src/lib.rs

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,61 +1478,27 @@ impl<S: MutinyStorage> MutinyWallet<S> {
14781478
return Err(MutinyError::WalletOperationFailed);
14791479
};
14801480

1481-
let (pj, ohttp) = {
1482-
use crate::payjoin::{OHTTP_RELAYS, PAYJOIN_DIR};
1483-
use anyhow::anyhow;
1484-
1485-
let ohttp_keys = crate::payjoin::fetch_ohttp_keys(
1486-
OHTTP_RELAYS[0].to_owned(),
1487-
PAYJOIN_DIR.to_owned(),
1488-
)
1489-
.await
1490-
.map_err(|e| anyhow!("Payjoin OHTTP fetch error {}", e))?;
1491-
1492-
let ohttp = base64::encode_config(
1493-
ohttp_keys
1494-
.encode()
1495-
.map_err(|_| MutinyError::PayjoinConfigError)?,
1496-
base64::URL_SAFE_NO_PAD,
1497-
);
1498-
let mut enroller = pj::receive::v2::Enroller::from_directory_config(
1499-
PAYJOIN_DIR.to_owned(),
1500-
ohttp_keys,
1501-
OHTTP_RELAYS[0].to_owned(), // TODO pick ohttp relay at random
1502-
);
1503-
1504-
// enroll client
1505-
let (req, context) = enroller.extract_req().unwrap();
1506-
let http_client = reqwest::Client::builder().build().unwrap();
1507-
let ohttp_response = http_client
1508-
.post(req.url)
1509-
.header("Content-Type", "message/ohttp-req")
1510-
.body(req.body)
1511-
.send()
1512-
.await
1513-
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
1514-
let ohttp_response = ohttp_response.bytes().await.unwrap();
1515-
let enrolled = enroller
1516-
.process_res(ohttp_response.as_ref(), context)
1517-
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
1518-
let session = self
1519-
.node_manager
1520-
.storage
1521-
.persist_payjoin(enrolled.clone())?;
1522-
let pj_uri = enrolled.fallback_target();
1523-
log_debug!(self.logger, "{pj_uri}");
1524-
let wallet = self.node_manager.wallet.clone();
1525-
let stop = self.node_manager.stop.clone();
1526-
let storage = Arc::new(self.node_manager.storage.clone());
1527-
// run await payjoin task in the background as it'll keep polling the relay
1528-
let logger = self.logger.clone();
1529-
utils::spawn(async move {
1530-
match NodeManager::receive_payjoin(wallet, stop, storage, session).await {
1531-
Ok(pj_txid) => log_info!(logger, "Received payjoin txid: {}", pj_txid),
1532-
Err(e) => log_error!(logger, "Payjoin error: {e}"),
1533-
}
1534-
});
1535-
(Some(pj_uri), Some(ohttp))
1481+
let (pj, ohttp) = match self.node_manager.start_payjoin_session().await {
1482+
Ok((enrolled, ohttp_keys)) => {
1483+
let session = self
1484+
.node_manager
1485+
.storage
1486+
.persist_payjoin(enrolled.clone())?;
1487+
let pj_uri = session.enrolled.fallback_target();
1488+
log_debug!(self.logger, "{pj_uri}");
1489+
self.node_manager.spawn_payjoin_receiver(session);
1490+
let ohttp = base64::encode_config(
1491+
ohttp_keys
1492+
.encode()
1493+
.map_err(|_| MutinyError::PayjoinConfigError)?,
1494+
base64::URL_SAFE_NO_PAD,
1495+
);
1496+
(Some(pj_uri), Some(ohttp))
1497+
}
1498+
Err(e) => {
1499+
log_error!(self.logger, "Error enrolling payjoin: {e}");
1500+
(None, None)
1501+
}
15361502
};
15371503

15381504
Ok(MutinyBip21RawMaterials {

mutiny-core/src/nodemanager.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::event::HTLCStatus;
33
use crate::labels::LabelStorage;
44
use crate::ldkstorage::CHANNEL_CLOSURE_PREFIX;
55
use crate::logging::LOGGING_KEY;
6-
use crate::payjoin::PayjoinStorage;
6+
use crate::payjoin::{Error as PayjoinError, PayjoinStorage};
77
use crate::utils::{sleep, spawn};
88
use crate::ActivityItem;
99
use crate::MutinyInvoice;
@@ -55,7 +55,9 @@ use lightning::util::logger::*;
5555
use lightning::{log_debug, log_error, log_info, log_trace, log_warn};
5656
use lightning_invoice::Bolt11Invoice;
5757
use lightning_transaction_sync::EsploraSyncClient;
58+
use payjoin::receive::v2::Enrolled;
5859
use payjoin::Uri;
60+
use pj::OhttpKeys;
5961
use reqwest::Client;
6062
use serde::{Deserialize, Serialize};
6163
use serde_json::Value;
@@ -633,15 +635,7 @@ impl<S: MutinyStorage> NodeManager<S> {
633635
pub(crate) fn resume_payjoins(nm: Arc<NodeManager<S>>) {
634636
let all = nm.storage.get_payjoins().unwrap_or_default();
635637
for payjoin in all {
636-
let wallet = nm.wallet.clone();
637-
let stop = nm.stop.clone();
638-
let storage = Arc::new(nm.storage.clone());
639-
utils::spawn(async move {
640-
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
641-
.await
642-
.unwrap();
643-
log::info!("Received payjoin txid: {}", pj_txid);
644-
});
638+
nm.clone().spawn_payjoin_receiver(payjoin);
645639
}
646640
}
647641

@@ -734,6 +728,34 @@ impl<S: MutinyStorage> NodeManager<S> {
734728
Err(MutinyError::WalletOperationFailed)
735729
}
736730

731+
pub async fn start_payjoin_session(&self) -> Result<(Enrolled, OhttpKeys), PayjoinError> {
732+
use crate::payjoin::{OHTTP_RELAYS, PAYJOIN_DIR};
733+
734+
let ohttp_keys =
735+
crate::payjoin::fetch_ohttp_keys(OHTTP_RELAYS[0].to_owned(), PAYJOIN_DIR.to_owned())
736+
.await?;
737+
let http_client = reqwest::Client::builder().build()?;
738+
739+
let mut enroller = payjoin::receive::v2::Enroller::from_directory_config(
740+
PAYJOIN_DIR.to_owned(),
741+
ohttp_keys.clone(),
742+
OHTTP_RELAYS[0].to_owned(), // TODO pick ohttp relay at random
743+
);
744+
// enroll client
745+
let (req, context) = enroller.extract_req()?;
746+
let ohttp_response = http_client
747+
.post(req.url)
748+
.header("Content-Type", "message/ohttp-req")
749+
.body(req.body)
750+
.send()
751+
.await?;
752+
let ohttp_response = ohttp_response.bytes().await?;
753+
Ok((
754+
enroller.process_res(ohttp_response.as_ref(), context)?,
755+
ohttp_keys,
756+
))
757+
}
758+
737759
// Send v1 payjoin request
738760
pub async fn send_payjoin(
739761
&self,
@@ -809,8 +831,21 @@ impl<S: MutinyStorage> NodeManager<S> {
809831
Ok(txid)
810832
}
811833

834+
pub fn spawn_payjoin_receiver(&self, session: crate::payjoin::Session) {
835+
let logger = self.logger.clone();
836+
let wallet = self.wallet.clone();
837+
let stop = self.stop.clone();
838+
let storage = Arc::new(self.storage.clone());
839+
utils::spawn(async move {
840+
match Self::receive_payjoin(wallet, stop, storage, session).await {
841+
Ok(txid) => log_info!(logger, "Received payjoin txid: {txid}"),
842+
Err(e) => log_error!(logger, "Error receiving payjoin: {e}"),
843+
};
844+
});
845+
}
846+
812847
/// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
813-
pub async fn receive_payjoin(
848+
async fn receive_payjoin(
814849
wallet: Arc<OnChainWallet<S>>,
815850
stop: Arc<AtomicBool>,
816851
storage: Arc<S>,
@@ -819,7 +854,9 @@ impl<S: MutinyStorage> NodeManager<S> {
819854
let http_client = reqwest::Client::builder().build()?;
820855
let proposal: payjoin::receive::v2::UncheckedProposal =
821856
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session).await?;
822-
let mut payjoin_proposal = wallet.process_payjoin_proposal(proposal).unwrap();
857+
let mut payjoin_proposal = wallet
858+
.process_payjoin_proposal(proposal)
859+
.map_err(PayjoinError::Wallet)?;
823860

824861
let (req, ohttp_ctx) = payjoin_proposal.extract_v2_req()?;
825862
let res = http_client

mutiny-core/src/payjoin.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,16 @@ impl<S: MutinyStorage> PayjoinStorage for S {
6969
}
7070
}
7171

72-
pub async fn fetch_ohttp_keys(
73-
_ohttp_relay: Url,
74-
directory: Url,
75-
) -> Result<OhttpKeys, Box<dyn std::error::Error>> {
72+
pub async fn fetch_ohttp_keys(_ohttp_relay: Url, directory: Url) -> Result<OhttpKeys, Error> {
7673
let http_client = reqwest::Client::builder().build().unwrap();
7774

7875
let ohttp_keys_res = http_client
7976
.get(format!("{}/ohttp-keys", directory.as_ref()))
8077
.send()
81-
.await
82-
.unwrap()
78+
.await?
8379
.bytes()
84-
.await
85-
.unwrap();
86-
Ok(OhttpKeys::decode(ohttp_keys_res.as_ref())?)
80+
.await?;
81+
Ok(OhttpKeys::decode(ohttp_keys_res.as_ref()).map_err(|_| Error::OhttpDecodeFailed)?)
8782
}
8883

8984
#[derive(Debug)]
@@ -92,6 +87,7 @@ pub enum Error {
9287
ReceiverStateMachine(payjoin::receive::Error),
9388
Wallet(payjoin::Error),
9489
Txid(bitcoin::hashes::hex::Error),
90+
OhttpDecodeFailed,
9591
Shutdown,
9692
SessionExpired,
9793
}
@@ -105,6 +101,7 @@ impl std::fmt::Display for Error {
105101
Error::ReceiverStateMachine(e) => write!(f, "Payjoin error: {}", e),
106102
Error::Wallet(e) => write!(f, "Payjoin wallet error: {}", e),
107103
Error::Txid(e) => write!(f, "Payjoin txid error: {}", e),
104+
Error::OhttpDecodeFailed => write!(f, "Failed to decode ohttp keys"),
108105
Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"),
109106
Error::SessionExpired => write!(f, "Payjoin session expired. Create a new payment request and have the sender try again."),
110107
}

0 commit comments

Comments
 (0)