Skip to content

Commit defa8b5

Browse files
committed
Seperate v2 cli logic into mod and ohttp components
This allows for easier review of the logic in the payjoin-cli as it relates to the v2 relay fallback logic. This change come in before the deprecation and removal of _danger-local-https for even simpler logic here.
1 parent 7c9e7ee commit defa8b5

File tree

2 files changed

+173
-162
lines changed

2 files changed

+173
-162
lines changed
Lines changed: 2 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,12 @@ use tokio::sync::watch;
1313
use super::config::Config;
1414
use super::wallet::BitcoindWallet;
1515
use super::App as AppTrait;
16+
use crate::app::v2::ohttp::{unwrap_ohttp_keys_or_else_fetch, validate_relay, RelayState};
1617
use crate::app::{handle_interrupt, http_agent};
1718
use crate::db::v2::{ReceiverPersister, SenderPersister};
1819
use crate::db::Database;
1920

20-
#[derive(Debug, Clone)]
21-
pub struct RelayState {
22-
selected_relay: Option<payjoin::Url>,
23-
#[cfg(not(feature = "_danger-local-https"))]
24-
failed_relays: Vec<payjoin::Url>,
25-
}
26-
27-
impl RelayState {
28-
#[cfg(feature = "_danger-local-https")]
29-
pub fn new() -> Self { RelayState { selected_relay: None } }
30-
#[cfg(not(feature = "_danger-local-https"))]
31-
pub fn new() -> Self { RelayState { selected_relay: None, failed_relays: Vec::new() } }
32-
33-
#[cfg(not(feature = "_danger-local-https"))]
34-
pub fn set_selected_relay(&mut self, relay: payjoin::Url) { self.selected_relay = Some(relay); }
35-
36-
pub fn get_selected_relay(&self) -> Option<payjoin::Url> { self.selected_relay.clone() }
37-
38-
#[cfg(not(feature = "_danger-local-https"))]
39-
pub fn add_failed_relay(&mut self, relay: payjoin::Url) { self.failed_relays.push(relay); }
40-
41-
#[cfg(not(feature = "_danger-local-https"))]
42-
pub fn get_failed_relays(&self) -> Vec<payjoin::Url> { self.failed_relays.clone() }
43-
}
21+
mod ohttp;
4422

4523
#[derive(Clone)]
4624
pub(crate) struct App {
@@ -376,144 +354,6 @@ fn try_contributing_inputs(
376354
.commit_inputs())
377355
}
378356

379-
async fn unwrap_ohttp_keys_or_else_fetch(
380-
config: &Config,
381-
relay_state: Arc<Mutex<RelayState>>,
382-
) -> Result<payjoin::OhttpKeys> {
383-
if let Some(keys) = config.v2()?.ohttp_keys.clone() {
384-
println!("Using OHTTP Keys from config");
385-
Ok(keys)
386-
} else {
387-
println!("Bootstrapping private network transport over Oblivious HTTP");
388-
389-
fetch_keys(config, relay_state.clone())
390-
.await
391-
.and_then(|keys| keys.ok_or_else(|| anyhow::anyhow!("No OHTTP keys found")))
392-
}
393-
}
394-
395-
#[cfg(not(feature = "_danger-local-https"))]
396-
async fn fetch_keys(
397-
config: &Config,
398-
relay_state: Arc<Mutex<RelayState>>,
399-
) -> Result<Option<payjoin::OhttpKeys>> {
400-
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
401-
let payjoin_directory = config.v2()?.pj_directory.clone();
402-
let relays = config.v2()?.ohttp_relays.clone();
403-
404-
loop {
405-
let failed_relays =
406-
relay_state.lock().expect("Lock should not be poisoned").get_failed_relays();
407-
408-
let remaining_relays: Vec<_> =
409-
relays.iter().filter(|r| !failed_relays.contains(r)).cloned().collect();
410-
411-
if remaining_relays.is_empty() {
412-
return Err(anyhow!("No valid relays available"));
413-
}
414-
415-
let selected_relay =
416-
match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) {
417-
Some(relay) => relay.clone(),
418-
None => return Err(anyhow!("Failed to select from remaining relays")),
419-
};
420-
421-
relay_state
422-
.lock()
423-
.expect("Lock should not be poisoned")
424-
.set_selected_relay(selected_relay.clone());
425-
426-
let ohttp_keys = {
427-
payjoin::io::fetch_ohttp_keys(selected_relay.clone(), payjoin_directory.clone()).await
428-
};
429-
430-
match ohttp_keys {
431-
Ok(keys) => return Ok(Some(keys)),
432-
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
433-
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
434-
}
435-
Err(e) => {
436-
log::debug!("Failed to connect to relay: {selected_relay}, {e:?}");
437-
relay_state
438-
.lock()
439-
.expect("Lock should not be poisoned")
440-
.add_failed_relay(selected_relay);
441-
}
442-
}
443-
}
444-
}
445-
446-
///Local relays are incapable of acting as proxies so we must opportunistically fetch keys from the config
447-
#[cfg(feature = "_danger-local-https")]
448-
async fn fetch_keys(
449-
config: &Config,
450-
_relay_state: Arc<Mutex<RelayState>>,
451-
) -> Result<Option<payjoin::OhttpKeys>> {
452-
let keys = config.v2()?.ohttp_keys.clone().expect("No OHTTP keys set");
453-
454-
Ok(Some(keys))
455-
}
456-
457-
#[cfg(not(feature = "_danger-local-https"))]
458-
async fn validate_relay(
459-
config: &Config,
460-
relay_state: Arc<Mutex<RelayState>>,
461-
) -> Result<payjoin::Url> {
462-
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
463-
let payjoin_directory = config.v2()?.pj_directory.clone();
464-
let relays = config.v2()?.ohttp_relays.clone();
465-
466-
loop {
467-
let failed_relays =
468-
relay_state.lock().expect("Lock should not be poisoned").get_failed_relays();
469-
470-
let remaining_relays: Vec<_> =
471-
relays.iter().filter(|r| !failed_relays.contains(r)).cloned().collect();
472-
473-
if remaining_relays.is_empty() {
474-
return Err(anyhow!("No valid relays available"));
475-
}
476-
477-
let selected_relay =
478-
match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) {
479-
Some(relay) => relay.clone(),
480-
None => return Err(anyhow!("Failed to select from remaining relays")),
481-
};
482-
483-
relay_state
484-
.lock()
485-
.expect("Lock should not be poisoned")
486-
.set_selected_relay(selected_relay.clone());
487-
488-
let ohttp_keys =
489-
payjoin::io::fetch_ohttp_keys(selected_relay.clone(), payjoin_directory.clone()).await;
490-
491-
match ohttp_keys {
492-
Ok(_) => return Ok(selected_relay),
493-
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
494-
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
495-
}
496-
Err(e) => {
497-
log::debug!("Failed to connect to relay: {selected_relay}, {e:?}");
498-
relay_state
499-
.lock()
500-
.expect("Lock should not be poisoned")
501-
.add_failed_relay(selected_relay);
502-
}
503-
}
504-
}
505-
}
506-
507-
#[cfg(feature = "_danger-local-https")]
508-
async fn validate_relay(
509-
config: &Config,
510-
_relay_state: Arc<Mutex<RelayState>>,
511-
) -> Result<payjoin::Url> {
512-
let relay = config.v2()?.ohttp_relays.first().expect("no OHTTP relay set").clone();
513-
514-
Ok(relay)
515-
}
516-
517357
async fn post_request(req: payjoin::Request) -> Result<reqwest::Response> {
518358
let http = http_agent()?;
519359
http.post(req.url)

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

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use std::sync::{Arc, Mutex};
2+
3+
#[cfg(feature = "_danger-local-https")]
4+
use anyhow::Result;
5+
#[cfg(not(feature = "_danger-local-https"))]
6+
use anyhow::{anyhow, Result};
7+
8+
use super::Config;
9+
10+
#[derive(Debug, Clone)]
11+
pub struct RelayState {
12+
selected_relay: Option<payjoin::Url>,
13+
#[cfg(not(feature = "_danger-local-https"))]
14+
failed_relays: Vec<payjoin::Url>,
15+
}
16+
17+
impl RelayState {
18+
#[cfg(feature = "_danger-local-https")]
19+
pub fn new() -> Self { RelayState { selected_relay: None } }
20+
#[cfg(not(feature = "_danger-local-https"))]
21+
pub fn new() -> Self { RelayState { selected_relay: None, failed_relays: Vec::new() } }
22+
23+
#[cfg(not(feature = "_danger-local-https"))]
24+
pub fn set_selected_relay(&mut self, relay: payjoin::Url) { self.selected_relay = Some(relay); }
25+
26+
pub fn get_selected_relay(&self) -> Option<payjoin::Url> { self.selected_relay.clone() }
27+
28+
#[cfg(not(feature = "_danger-local-https"))]
29+
pub fn add_failed_relay(&mut self, relay: payjoin::Url) { self.failed_relays.push(relay); }
30+
31+
#[cfg(not(feature = "_danger-local-https"))]
32+
pub fn get_failed_relays(&self) -> Vec<payjoin::Url> { self.failed_relays.clone() }
33+
}
34+
35+
pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(
36+
config: &Config,
37+
relay_state: Arc<Mutex<RelayState>>,
38+
) -> Result<payjoin::OhttpKeys> {
39+
if let Some(keys) = config.v2()?.ohttp_keys.clone() {
40+
println!("Using OHTTP Keys from config");
41+
Ok(keys)
42+
} else {
43+
println!("Bootstrapping private network transport over Oblivious HTTP");
44+
45+
fetch_keys(config, relay_state.clone())
46+
.await
47+
.and_then(|keys| keys.ok_or_else(|| anyhow::anyhow!("No OHTTP keys found")))
48+
}
49+
}
50+
51+
#[cfg(not(feature = "_danger-local-https"))]
52+
async fn fetch_keys(
53+
config: &Config,
54+
relay_state: Arc<Mutex<RelayState>>,
55+
) -> Result<Option<payjoin::OhttpKeys>> {
56+
use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
57+
let payjoin_directory = config.v2()?.pj_directory.clone();
58+
let relays = config.v2()?.ohttp_relays.clone();
59+
60+
loop {
61+
let failed_relays =
62+
relay_state.lock().expect("Lock should not be poisoned").get_failed_relays();
63+
64+
let remaining_relays: Vec<_> =
65+
relays.iter().filter(|r| !failed_relays.contains(r)).cloned().collect();
66+
67+
if remaining_relays.is_empty() {
68+
return Err(anyhow!("No valid relays available"));
69+
}
70+
71+
let selected_relay =
72+
match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) {
73+
Some(relay) => relay.clone(),
74+
None => return Err(anyhow!("Failed to select from remaining relays")),
75+
};
76+
77+
relay_state
78+
.lock()
79+
.expect("Lock should not be poisoned")
80+
.set_selected_relay(selected_relay.clone());
81+
82+
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());
90+
}
91+
Err(e) => {
92+
log::debug!("Failed to connect to relay: {selected_relay}, {e:?}");
93+
relay_state
94+
.lock()
95+
.expect("Lock should not be poisoned")
96+
.add_failed_relay(selected_relay);
97+
}
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_state: Arc<Mutex<RelayState>>,
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_state: Arc<Mutex<RelayState>>,
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_state.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_state
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;
146+
147+
match ohttp_keys {
148+
Ok(_) => return Ok(selected_relay),
149+
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
150+
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
151+
}
152+
Err(e) => {
153+
log::debug!("Failed to connect to relay: {selected_relay}, {e:?}");
154+
relay_state
155+
.lock()
156+
.expect("Lock should not be poisoned")
157+
.add_failed_relay(selected_relay);
158+
}
159+
}
160+
}
161+
}
162+
163+
#[cfg(feature = "_danger-local-https")]
164+
pub(crate) async fn validate_relay(
165+
config: &Config,
166+
_relay_state: Arc<Mutex<RelayState>>,
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)