Skip to content

Commit c35b20a

Browse files
authored
Merge parallel feature flag local certificate logic in cli ohttp (payjoin#726)
These ohttp functions had parallel methods when running tests vs running in production. This made it difficult to read as there were two functions that appeared to do completely different things when a feature flag was present. Because localhost relays are not actually able to be proxies here we still need to opportunistically select them from the config when running our tests instead of just allowing local cert validation. ``` [2025-05-30T17:17:53Z DEBUG payjoin_cli::app::v2::ohttp] Failed to connect to relay: http://localhost:34391/, Internal(InternalError(Reqwest(reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("payjo.in")), port: None, path: "/.well-known/ohttp-gateway", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, "unsuccessful tunnel") }))) ```
2 parents d8c37c0 + 6e03ab1 commit c35b20a

File tree

2 files changed

+52
-99
lines changed

2 files changed

+52
-99
lines changed

payjoin-cli/src/app/v2/mod.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tokio::sync::watch;
1616
use super::config::Config;
1717
use super::wallet::BitcoindWallet;
1818
use super::App as AppTrait;
19-
use crate::app::v2::ohttp::{unwrap_ohttp_keys_or_else_fetch, validate_relay, RelayManager};
19+
use crate::app::v2::ohttp::{unwrap_ohttp_keys_or_else_fetch, RelayManager};
2020
use crate::app::{handle_interrupt, http_agent};
2121
use crate::db::v2::{ReceiverPersister, SenderPersister};
2222
use crate::db::Database;
@@ -78,7 +78,9 @@ impl AppTrait for App {
7878
async fn receive_payjoin(&self, amount: Amount) -> Result<()> {
7979
let address = self.wallet().get_new_address()?;
8080
let ohttp_keys =
81-
unwrap_ohttp_keys_or_else_fetch(&self.config, self.relay_manager.clone()).await?;
81+
unwrap_ohttp_keys_or_else_fetch(&self.config, None, self.relay_manager.clone())
82+
.await?
83+
.ohttp_keys;
8284
let mut persister = ReceiverPersister::new(self.db.clone());
8385
let new_receiver = NewReceiver::new(
8486
address,
@@ -160,7 +162,9 @@ impl App {
160162
println!("Receive session established");
161163
let mut pj_uri = session.pj_uri();
162164
pj_uri.amount = amount;
163-
let ohttp_relay = self.unwrap_relay_or_else_fetch().await?;
165+
let ohttp_relay = self
166+
.unwrap_relay_or_else_fetch(Some(session.pj_uri().extras.endpoint().clone()))
167+
.await?;
164168

165169
println!("Request Payjoin by sharing this Payjoin Uri:");
166170
println!("{pj_uri}");
@@ -201,7 +205,7 @@ impl App {
201205
}
202206

203207
async fn long_poll_post(&self, req_ctx: &mut Sender<WithReplyKey>) -> Result<Psbt> {
204-
let ohttp_relay = self.unwrap_relay_or_else_fetch().await?;
208+
let ohttp_relay = self.unwrap_relay_or_else_fetch(Some(req_ctx.endpoint().clone())).await?;
205209

206210
match req_ctx.extract_v2(ohttp_relay.clone()) {
207211
Ok((req, ctx)) => {
@@ -246,7 +250,9 @@ impl App {
246250
&self,
247251
session: &mut Receiver<WithContext>,
248252
) -> Result<Receiver<UncheckedProposal>> {
249-
let ohttp_relay = self.unwrap_relay_or_else_fetch().await?;
253+
let ohttp_relay = self
254+
.unwrap_relay_or_else_fetch(Some(session.pj_uri().extras.endpoint().clone()))
255+
.await?;
250256

251257
loop {
252258
let (req, context) = session.extract_req(&ohttp_relay)?;
@@ -302,12 +308,18 @@ impl App {
302308
Ok(payjoin_proposal)
303309
}
304310

305-
async fn unwrap_relay_or_else_fetch(&self) -> Result<payjoin::Url> {
311+
async fn unwrap_relay_or_else_fetch(
312+
&self,
313+
directory: Option<payjoin::Url>,
314+
) -> Result<payjoin::Url> {
306315
let selected_relay =
307316
self.relay_manager.lock().expect("Lock should not be poisoned").get_selected_relay();
308317
let ohttp_relay = match selected_relay {
309318
Some(relay) => relay,
310-
None => validate_relay(&self.config, self.relay_manager.clone()).await?,
319+
None =>
320+
unwrap_ohttp_keys_or_else_fetch(&self.config, directory, self.relay_manager.clone())
321+
.await?
322+
.relay_url,
311323
};
312324
Ok(ohttp_relay)
313325
}

payjoin-cli/src/app/v2/ohttp.rs

Lines changed: 33 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,58 @@
11
use std::sync::{Arc, Mutex};
22

3-
#[cfg(feature = "_danger-local-https")]
4-
use anyhow::Result;
5-
#[cfg(not(feature = "_danger-local-https"))]
63
use anyhow::{anyhow, Result};
74

85
use super::Config;
96

107
#[derive(Debug, Clone)]
118
pub struct RelayManager {
129
selected_relay: Option<payjoin::Url>,
13-
#[cfg(not(feature = "_danger-local-https"))]
1410
failed_relays: Vec<payjoin::Url>,
1511
}
1612

1713
impl RelayManager {
18-
#[cfg(feature = "_danger-local-https")]
19-
pub fn new() -> Self { RelayManager { selected_relay: None } }
20-
#[cfg(not(feature = "_danger-local-https"))]
2114
pub fn new() -> Self { RelayManager { selected_relay: None, failed_relays: Vec::new() } }
2215

23-
#[cfg(not(feature = "_danger-local-https"))]
2416
pub fn set_selected_relay(&mut self, relay: payjoin::Url) { self.selected_relay = Some(relay); }
2517

2618
pub fn get_selected_relay(&self) -> Option<payjoin::Url> { self.selected_relay.clone() }
2719

28-
#[cfg(not(feature = "_danger-local-https"))]
2920
pub fn add_failed_relay(&mut self, relay: payjoin::Url) { self.failed_relays.push(relay); }
3021

31-
#[cfg(not(feature = "_danger-local-https"))]
3222
pub fn get_failed_relays(&self) -> Vec<payjoin::Url> { self.failed_relays.clone() }
3323
}
3424

25+
pub(crate) struct ValidatedOhttpKeys {
26+
pub(crate) ohttp_keys: payjoin::OhttpKeys,
27+
pub(crate) relay_url: payjoin::Url,
28+
}
29+
3530
pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(
3631
config: &Config,
32+
directory: Option<payjoin::Url>,
3733
relay_manager: Arc<Mutex<RelayManager>>,
38-
) -> Result<payjoin::OhttpKeys> {
39-
if let Some(keys) = config.v2()?.ohttp_keys.clone() {
34+
) -> Result<ValidatedOhttpKeys> {
35+
if let Some(ohttp_keys) = config.v2()?.ohttp_keys.clone() {
4036
println!("Using OHTTP Keys from config");
41-
Ok(keys)
37+
return Ok(ValidatedOhttpKeys {
38+
ohttp_keys,
39+
relay_url: config.v2()?.ohttp_relays[0].clone(),
40+
});
4241
} else {
4342
println!("Bootstrapping private network transport over Oblivious HTTP");
43+
let fetched_keys = fetch_ohttp_keys(config, directory, relay_manager).await?;
4444

45-
fetch_keys(config, relay_manager.clone())
46-
.await
47-
.and_then(|keys| keys.ok_or_else(|| anyhow::anyhow!("No OHTTP keys found")))
45+
Ok(fetched_keys)
4846
}
4947
}
5048

51-
#[cfg(not(feature = "_danger-local-https"))]
52-
async fn fetch_keys(
49+
async fn fetch_ohttp_keys(
5350
config: &Config,
51+
directory: Option<payjoin::Url>,
5452
relay_manager: Arc<Mutex<RelayManager>>,
55-
) -> Result<Option<payjoin::OhttpKeys>> {
53+
) -> Result<ValidatedOhttpKeys> {
5654
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
57-
let payjoin_directory = config.v2()?.pj_directory.clone();
55+
let payjoin_directory = directory.unwrap_or(config.v2()?.pj_directory.clone());
5856
let relays = config.v2()?.ohttp_relays.clone();
5957

6058
loop {
@@ -80,72 +78,25 @@ async fn fetch_keys(
8078
.set_selected_relay(selected_relay.clone());
8179

8280
let ohttp_keys = {
83-
payjoin::io::fetch_ohttp_keys(selected_relay.clone(), payjoin_directory.clone()).await
84-
};
85-
86-
match ohttp_keys {
87-
Ok(keys) => return Ok(Some(keys)),
88-
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
89-
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
81+
#[cfg(feature = "_danger-local-https")]
82+
{
83+
let cert_der = crate::app::read_local_cert()?;
84+
payjoin::io::fetch_ohttp_keys_with_cert(
85+
&selected_relay,
86+
&payjoin_directory,
87+
cert_der,
88+
)
89+
.await
9090
}
91-
Err(e) => {
92-
log::debug!("Failed to connect to relay: {selected_relay}, {e:?}");
93-
relay_manager
94-
.lock()
95-
.expect("Lock should not be poisoned")
96-
.add_failed_relay(selected_relay);
91+
#[cfg(not(feature = "_danger-local-https"))]
92+
{
93+
payjoin::io::fetch_ohttp_keys(&selected_relay, &payjoin_directory).await
9794
}
98-
}
99-
}
100-
}
101-
102-
///Local relays are incapable of acting as proxies so we must opportunistically fetch keys from the config
103-
#[cfg(feature = "_danger-local-https")]
104-
async fn fetch_keys(
105-
config: &Config,
106-
_relay_manager: Arc<Mutex<RelayManager>>,
107-
) -> Result<Option<payjoin::OhttpKeys>> {
108-
let keys = config.v2()?.ohttp_keys.clone().expect("No OHTTP keys set");
109-
110-
Ok(Some(keys))
111-
}
112-
113-
#[cfg(not(feature = "_danger-local-https"))]
114-
pub(crate) async fn validate_relay(
115-
config: &Config,
116-
relay_manager: Arc<Mutex<RelayManager>>,
117-
) -> Result<payjoin::Url> {
118-
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
119-
let payjoin_directory = config.v2()?.pj_directory.clone();
120-
let relays = config.v2()?.ohttp_relays.clone();
121-
122-
loop {
123-
let failed_relays =
124-
relay_manager.lock().expect("Lock should not be poisoned").get_failed_relays();
125-
126-
let remaining_relays: Vec<_> =
127-
relays.iter().filter(|r| !failed_relays.contains(r)).cloned().collect();
128-
129-
if remaining_relays.is_empty() {
130-
return Err(anyhow!("No valid relays available"));
131-
}
132-
133-
let selected_relay =
134-
match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) {
135-
Some(relay) => relay.clone(),
136-
None => return Err(anyhow!("Failed to select from remaining relays")),
137-
};
138-
139-
relay_manager
140-
.lock()
141-
.expect("Lock should not be poisoned")
142-
.set_selected_relay(selected_relay.clone());
143-
144-
let ohttp_keys =
145-
payjoin::io::fetch_ohttp_keys(selected_relay.clone(), payjoin_directory.clone()).await;
95+
};
14696

14797
match ohttp_keys {
148-
Ok(_) => return Ok(selected_relay),
98+
Ok(keys) =>
99+
return Ok(ValidatedOhttpKeys { ohttp_keys: keys, relay_url: selected_relay }),
149100
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
150101
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
151102
}
@@ -159,13 +110,3 @@ pub(crate) async fn validate_relay(
159110
}
160111
}
161112
}
162-
163-
#[cfg(feature = "_danger-local-https")]
164-
pub(crate) async fn validate_relay(
165-
config: &Config,
166-
_relay_manager: Arc<Mutex<RelayManager>>,
167-
) -> Result<payjoin::Url> {
168-
let relay = config.v2()?.ohttp_relays.first().expect("no OHTTP relay set").clone();
169-
170-
Ok(relay)
171-
}

0 commit comments

Comments
 (0)