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

Commit a8a24f9

Browse files
committed
Enable payjoin expiration
1 parent 70ed57f commit a8a24f9

3 files changed

Lines changed: 56 additions & 16 deletions

File tree

mutiny-core/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,17 +1550,19 @@ impl<S: MutinyStorage> MutinyWallet<S> {
15501550
let enrolled = enroller
15511551
.process_res(ohttp_response.as_ref(), context)
15521552
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
1553-
self.node_manager
1553+
let session = self
1554+
.node_manager
15541555
.storage
15551556
.persist_payjoin(enrolled.clone())?;
15561557
let pj_uri = enrolled.fallback_target();
15571558
log_debug!(self.logger, "{pj_uri}");
15581559
let wallet = self.node_manager.wallet.clone();
15591560
let stop = self.node_manager.stop.clone();
1561+
let storage = Arc::new(self.node_manager.storage.clone());
15601562
// run await payjoin task in the background as it'll keep polling the relay
15611563
let logger = self.logger.clone();
15621564
utils::spawn(async move {
1563-
match NodeManager::receive_payjoin(wallet, stop, enrolled).await {
1565+
match NodeManager::receive_payjoin(wallet, stop, storage, session).await {
15641566
Ok(pj_txid) => log_info!(logger, "Received payjoin txid: {}", pj_txid),
15651567
Err(e) => log_error!(logger, "Payjoin error: {e}"),
15661568
}

mutiny-core/src/nodemanager.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,11 @@ impl<S: MutinyStorage> NodeManager<S> {
635635
for payjoin in all {
636636
let wallet = nm.wallet.clone();
637637
let stop = nm.stop.clone();
638+
let storage = Arc::new(nm.storage.clone());
638639
utils::spawn(async move {
639-
let pj_txid = Self::receive_payjoin(wallet, stop, payjoin).await.unwrap();
640+
let pj_txid = Self::receive_payjoin(wallet, stop, storage, payjoin)
641+
.await
642+
.unwrap();
640643
log::info!("Received payjoin txid: {}", pj_txid);
641644
});
642645
}
@@ -810,15 +813,16 @@ impl<S: MutinyStorage> NodeManager<S> {
810813
pub async fn receive_payjoin(
811814
wallet: Arc<OnChainWallet<S>>,
812815
stop: Arc<AtomicBool>,
813-
mut enrolled: payjoin::receive::v2::Enrolled,
816+
storage: Arc<S>,
817+
mut session: crate::payjoin::Session,
814818
) -> Result<Txid, MutinyError> {
815819
use crate::payjoin::Error as PayjoinError;
816820

817821
let http_client = reqwest::Client::builder()
818822
.build()
819823
.map_err(PayjoinError::Reqwest)?;
820824
let proposal: payjoin::receive::v2::UncheckedProposal =
821-
Self::poll_for_fallback_psbt(stop, &http_client, &mut enrolled)
825+
Self::poll_for_fallback_psbt(stop, storage, &http_client, &mut session)
822826
.await
823827
.map_err(|e| PayjoinError::ReceiverStateMachine(e.to_string()))?;
824828
let mut payjoin_proposal = wallet
@@ -845,22 +849,30 @@ impl<S: MutinyStorage> NodeManager<S> {
845849

846850
async fn poll_for_fallback_psbt(
847851
stop: Arc<AtomicBool>,
852+
storage: Arc<S>,
848853
client: &reqwest::Client,
849-
enroller: &mut payjoin::receive::v2::Enrolled,
854+
session: &mut crate::payjoin::Session,
850855
) -> Result<payjoin::receive::v2::UncheckedProposal, crate::payjoin::Error> {
851856
loop {
852857
if stop.load(Ordering::Relaxed) {
853858
return Err(crate::payjoin::Error::Shutdown);
854859
}
855-
let (req, context) = enroller.extract_req()?;
860+
861+
if session.expiry < utils::now() {
862+
let _ = storage.delete_payjoin(&session.enrolled.pubkey());
863+
return Err(crate::payjoin::Error::SessionExpired);
864+
}
865+
let (req, context) = session.enrolled.extract_req()?;
856866
let ohttp_response = client
857867
.post(req.url)
858868
.header("Content-Type", "message/ohttp-req")
859869
.body(req.body)
860870
.send()
861871
.await?;
862872
let ohttp_response = ohttp_response.bytes().await?;
863-
let proposal = enroller.process_res(ohttp_response.as_ref(), context)?;
873+
let proposal = session
874+
.enrolled
875+
.process_res(ohttp_response.as_ref(), context)?;
864876
match proposal {
865877
Some(proposal) => return Ok(proposal),
866878
None => utils::sleep(5000).await,

mutiny-core/src/payjoin.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ use std::collections::HashMap;
22

33
use crate::error::MutinyError;
44
use crate::storage::MutinyStorage;
5+
use core::time::Duration;
56
use hex_conservative::DisplayHex;
67
use once_cell::sync::Lazy;
78
use payjoin::receive::v2::Enrolled;
89
use payjoin::OhttpKeys;
10+
use serde::{Deserialize, Serialize};
911
use url::Url;
1012

1113
pub(crate) static OHTTP_RELAYS: [Lazy<Url>; 2] = [
@@ -16,10 +18,22 @@ pub(crate) static OHTTP_RELAYS: [Lazy<Url>; 2] = [
1618
pub(crate) static PAYJOIN_DIR: Lazy<Url> =
1719
Lazy::new(|| Url::parse("https://payjo.in").expect("Invalid URL"));
1820

21+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22+
pub struct Session {
23+
pub enrolled: Enrolled,
24+
pub expiry: Duration,
25+
}
26+
27+
impl Session {
28+
pub fn pubkey(&self) -> [u8; 33] {
29+
self.enrolled.pubkey()
30+
}
31+
}
1932
pub trait PayjoinStorage {
20-
fn get_payjoin(&self, id: &[u8; 33]) -> Result<Option<Enrolled>, MutinyError>;
21-
fn get_payjoins(&self) -> Result<Vec<Enrolled>, MutinyError>;
22-
fn persist_payjoin(&self, session: Enrolled) -> Result<(), MutinyError>;
33+
fn get_payjoin(&self, id: &[u8; 33]) -> Result<Option<Session>, MutinyError>;
34+
fn get_payjoins(&self) -> Result<Vec<Session>, MutinyError>;
35+
fn persist_payjoin(&self, session: Enrolled) -> Result<Session, MutinyError>;
36+
fn delete_payjoin(&self, id: &[u8; 33]) -> Result<(), MutinyError>;
2337
}
2438

2539
const PAYJOIN_KEY_PREFIX: &str = "payjoin/";
@@ -29,18 +43,28 @@ fn get_payjoin_key(id: &[u8; 33]) -> String {
2943
}
3044

3145
impl<S: MutinyStorage> PayjoinStorage for S {
32-
fn get_payjoin(&self, id: &[u8; 33]) -> Result<Option<Enrolled>, MutinyError> {
46+
fn get_payjoin(&self, id: &[u8; 33]) -> Result<Option<Session>, MutinyError> {
3347
let sessions = self.get_data(get_payjoin_key(id))?;
3448
Ok(sessions)
3549
}
3650

37-
fn get_payjoins(&self) -> Result<Vec<Enrolled>, MutinyError> {
38-
let map: HashMap<String, Enrolled> = self.scan(PAYJOIN_KEY_PREFIX, None)?;
51+
fn get_payjoins(&self) -> Result<Vec<Session>, MutinyError> {
52+
let map: HashMap<String, Session> = self.scan(PAYJOIN_KEY_PREFIX, None)?;
3953
Ok(map.values().map(|v| v.to_owned()).collect())
4054
}
4155

42-
fn persist_payjoin(&self, session: Enrolled) -> Result<(), MutinyError> {
43-
self.set_data(get_payjoin_key(&session.pubkey()), session, None)
56+
fn persist_payjoin(&self, enrolled: Enrolled) -> Result<Session, MutinyError> {
57+
let in_24_hours = crate::utils::now() + Duration::from_secs(60 * 60 * 24);
58+
let session = Session {
59+
enrolled,
60+
expiry: in_24_hours,
61+
};
62+
self.set_data(get_payjoin_key(&session.pubkey()), session.clone(), None)
63+
.map(|_| session)
64+
}
65+
66+
fn delete_payjoin(&self, id: &[u8; 33]) -> Result<(), MutinyError> {
67+
self.delete(&[get_payjoin_key(id)])
4468
}
4569
}
4670

@@ -65,6 +89,7 @@ pub enum Error {
6589
ReceiverStateMachine(String),
6690
Txid(bitcoin::hashes::hex::Error),
6791
Shutdown,
92+
SessionExpired,
6893
}
6994

7095
impl std::error::Error for Error {}
@@ -76,6 +101,7 @@ impl std::fmt::Display for Error {
76101
Error::ReceiverStateMachine(e) => write!(f, "Payjoin state machine error: {}", e),
77102
Error::Txid(e) => write!(f, "Payjoin txid error: {}", e),
78103
Error::Shutdown => write!(f, "Payjoin stopped by application shutdown"),
104+
Error::SessionExpired => write!(f, "Payjoin session expired. Create a new payment request and have the sender try again."),
79105
}
80106
}
81107
}

0 commit comments

Comments
 (0)