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

payjoin / rust-payjoin / 21282967115

23 Jan 2026 10:28AM UTC coverage: 83.168% (-0.04%) from 83.203%
21282967115

Pull #1294

github

web-flow
Merge aa79513f9 into 45689ba2c
Pull Request #1294: Add directory server fallback for improved network resilience

33 of 55 new or added lines in 2 files covered. (60.0%)

69 existing lines in 3 files now uncovered.

10144 of 12197 relevant lines covered (83.17%)

431.95 hits per line

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

69.74
/payjoin-cli/src/app/v2/ohttp.rs
1
use std::sync::{Arc, Mutex};
2

3
use anyhow::{anyhow, Result};
4

5
use super::Config;
6

7
#[derive(Debug, Clone)]
8
pub struct RelayManager {
9
    selected_relay: Option<url::Url>,
10
    failed_relays: Vec<url::Url>,
11
}
12

13
impl RelayManager {
14
    pub fn new() -> Self { RelayManager { selected_relay: None, failed_relays: Vec::new() } }
8✔
15

16
    pub fn set_selected_relay(&mut self, relay: url::Url) { self.selected_relay = Some(relay); }
3✔
17

18
    pub fn get_selected_relay(&self) -> Option<url::Url> { self.selected_relay.clone() }
7✔
19

20
    pub fn add_failed_relay(&mut self, relay: url::Url) { self.failed_relays.push(relay); }
×
21

22
    pub fn get_failed_relays(&self) -> Vec<url::Url> { self.failed_relays.clone() }
3✔
23

NEW
24
    pub fn clear_failed_relays(&mut self) { self.failed_relays.clear(); }
×
25
}
26

27
pub(crate) struct ValidatedOhttpKeys {
28
    pub(crate) ohttp_keys: payjoin::OhttpKeys,
29
    pub(crate) relay_url: url::Url,
30
}
31

32
pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(
5✔
33
    config: &Config,
5✔
34
    directory: Option<url::Url>,
5✔
35
    relay_manager: Arc<Mutex<RelayManager>>,
5✔
36
) -> Result<ValidatedOhttpKeys> {
5✔
37
    if let Some(ohttp_keys) = config.v2()?.ohttp_keys.clone() {
5✔
38
        println!("Using OHTTP Keys from config");
2✔
39
        return Ok(ValidatedOhttpKeys {
40
            ohttp_keys,
2✔
41
            relay_url: config.v2()?.ohttp_relays[0].clone(),
2✔
42
        });
43
    } else {
44
        println!("Bootstrapping private network transport over Oblivious HTTP");
3✔
45
        let fetched_keys = fetch_ohttp_keys(config, directory, relay_manager).await?;
3✔
46

47
        Ok(fetched_keys)
3✔
48
    }
49
}
5✔
50

51
async fn fetch_ohttp_keys(
3✔
52
    config: &Config,
3✔
53
    directory: Option<url::Url>,
3✔
54
    relay_manager: Arc<Mutex<RelayManager>>,
3✔
55
) -> Result<ValidatedOhttpKeys> {
3✔
56
    use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom;
57
    let payjoin_directories = if let Some(dir) = directory {
3✔
58
        vec![dir]
3✔
59
    } else {
NEW
UNCOV
60
        config.v2()?.pj_directories.clone()
×
61
    };
62
    let relays = config.v2()?.ohttp_relays.clone();
3✔
63

64
    // Try each directory in order until one succeeds
65
    for payjoin_directory in &payjoin_directories {
3✔
66
        tracing::debug!("Trying directory: {}", payjoin_directory);
3✔
67
        
68
        loop {
69
            let failed_relays =
3✔
70
                relay_manager.lock().expect("Lock should not be poisoned").get_failed_relays();
3✔
71

72
            let remaining_relays: Vec<_> =
3✔
73
                relays.iter().filter(|r| !failed_relays.contains(r)).cloned().collect();
3✔
74

75
            if remaining_relays.is_empty() {
3✔
NEW
UNCOV
76
                tracing::debug!("No remaining relays for directory: {}", payjoin_directory);
×
NEW
77
                break; // Try next directory
×
78
            }
3✔
79

80
            let selected_relay =
3✔
81
                match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) {
3✔
82
                    Some(relay) => relay.clone(),
3✔
NEW
83
                    None => break, // Try next directory
×
84
                };
85

86
            relay_manager
3✔
87
                .lock()
3✔
88
                .expect("Lock should not be poisoned")
3✔
89
                .set_selected_relay(selected_relay.clone());
3✔
90

91
            let ohttp_keys = {
3✔
92
                #[cfg(feature = "_manual-tls")]
93
                {
94
                    if let Some(cert_path) = config.root_certificate.as_ref() {
3✔
95
                        let cert_der = std::fs::read(cert_path)?;
3✔
96
                        payjoin::io::fetch_ohttp_keys_with_cert(
3✔
97
                            selected_relay.as_str(),
3✔
98
                            payjoin_directory.as_str(),
3✔
99
                            cert_der,
3✔
100
                        )
3✔
101
                        .await
3✔
102
                    } else {
NEW
103
                        payjoin::io::fetch_ohttp_keys(
×
NEW
104
                            selected_relay.as_str(),
×
NEW
105
                            payjoin_directory.as_str(),
×
NEW
106
                        )
×
NEW
107
                        .await
×
108
                    }
109
                }
110
                #[cfg(not(feature = "_manual-tls"))]
111
                payjoin::io::fetch_ohttp_keys(selected_relay.as_str(), payjoin_directory.as_str()).await
112
            };
113

NEW
114
            match ohttp_keys {
×
115
                Ok(keys) => {
3✔
116
                    tracing::debug!("Successfully fetched keys from directory: {} via relay: {}", payjoin_directory, selected_relay);
3✔
117
                    return Ok(ValidatedOhttpKeys { ohttp_keys: keys, relay_url: selected_relay });
3✔
118
                }
NEW
119
                Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
×
NEW
120
                    tracing::debug!("Unexpected status code from directory: {}, error: {}", payjoin_directory, e);
×
NEW
121
                    break; // Try next directory
×
122
                }
NEW
123
                Err(e) => {
×
NEW
124
                    tracing::debug!("Failed to connect to relay: {} for directory: {}, error: {:?}", selected_relay, payjoin_directory, e);
×
NEW
125
                    relay_manager
×
NEW
126
                        .lock()
×
NEW
127
                        .expect("Lock should not be poisoned")
×
NEW
128
                        .add_failed_relay(selected_relay);
×
129
                    // Continue to next relay for this directory
130
                }
131
            }
132
        }
133
        
134
        // Reset failed relays for next directory attempt
NEW
135
        relay_manager.lock().expect("Lock should not be poisoned").clear_failed_relays();
×
136
    }
137

NEW
138
    Err(anyhow!("Failed to fetch OHTTP keys from all directories"))
×
139
}
3✔
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