• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 15192768590

22 May 2025 05:15PM UTC coverage: 83.525%. Remained the same
15192768590

push

github

web-flow
Seperate v2 cli logic into mod and ohttp components (#714)

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.

This will also help to simplify the diff in #705

18 of 31 new or added lines in 1 file covered. (58.06%)

5947 of 7120 relevant lines covered (83.53%)

650.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.06
/payjoin-cli/src/app/v2/ohttp.rs
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 } }
4✔
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() }
6✔
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(
1✔
36
    config: &Config,
1✔
37
    relay_state: Arc<Mutex<RelayState>>,
1✔
38
) -> Result<payjoin::OhttpKeys> {
1✔
39
    if let Some(keys) = config.v2()?.ohttp_keys.clone() {
1✔
40
        println!("Using OHTTP Keys from config");
1✔
41
        Ok(keys)
1✔
42
    } else {
NEW
43
        println!("Bootstrapping private network transport over Oblivious HTTP");
×
NEW
44

×
NEW
45
        fetch_keys(config, relay_state.clone())
×
NEW
46
            .await
×
NEW
47
            .and_then(|keys| keys.ok_or_else(|| anyhow::anyhow!("No OHTTP keys found")))
×
48
    }
49
}
1✔
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")]
NEW
104
async fn fetch_keys(
×
NEW
105
    config: &Config,
×
NEW
106
    _relay_state: Arc<Mutex<RelayState>>,
×
NEW
107
) -> Result<Option<payjoin::OhttpKeys>> {
×
NEW
108
    let keys = config.v2()?.ohttp_keys.clone().expect("No OHTTP keys set");
×
NEW
109

×
NEW
110
    Ok(Some(keys))
×
NEW
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(
6✔
165
    config: &Config,
6✔
166
    _relay_state: Arc<Mutex<RelayState>>,
6✔
167
) -> Result<payjoin::Url> {
6✔
168
    let relay = config.v2()?.ohttp_relays.first().expect("no OHTTP relay set").clone();
6✔
169

6✔
170
    Ok(relay)
6✔
171
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc