Skip to content

Commit 3744b03

Browse files
committed
Payjoin-cli should cache ohttp-keys for re-use
This pr adds the functionality of ohttp-keys catching to payjoin-cli , ohttp-keys should not be fetched each time and a cached key should be use. Keys expire in 6 months . fix broken test due cache key reuse fix broken test due cache key reuse Payjoin-cli should cache ohttp keys for re-use
1 parent 6a2386d commit 3744b03

2 files changed

Lines changed: 92 additions & 7 deletions

File tree

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

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
use std::fs;
2+
use std::path::PathBuf;
13
use std::sync::{Arc, Mutex};
4+
use std::time::{Duration, SystemTime};
25

36
use anyhow::{anyhow, Result};
7+
use serde::{Deserialize, Serialize};
48

59
use super::Config;
610

11+
// 6 months
12+
const CACHE_DURATION: Duration = Duration::from_secs(6 * 30 * 24 * 60 * 60);
13+
714
#[derive(Debug, Clone)]
815
pub struct RelayManager {
916
selected_relay: Option<payjoin::Url>,
@@ -32,18 +39,19 @@ pub(crate) async fn unwrap_ohttp_keys_or_else_fetch(
3239
directory: Option<payjoin::Url>,
3340
relay_manager: Arc<Mutex<RelayManager>>,
3441
) -> Result<ValidatedOhttpKeys> {
42+
println!("before first some");
3543
if let Some(ohttp_keys) = config.v2()?.ohttp_keys.clone() {
3644
println!("Using OHTTP Keys from config");
3745
return Ok(ValidatedOhttpKeys {
3846
ohttp_keys,
3947
relay_url: config.v2()?.ohttp_relays[0].clone(),
4048
});
41-
} else {
42-
println!("Bootstrapping private network transport over Oblivious HTTP");
43-
let fetched_keys = fetch_ohttp_keys(config, directory, relay_manager).await?;
44-
45-
Ok(fetched_keys)
4649
}
50+
51+
println!("Bootstrapping private network transport over Oblivious HTTP");
52+
let fetched_keys = fetch_ohttp_keys(config, directory, relay_manager).await?;
53+
54+
Ok(fetched_keys)
4755
}
4856

4957
async fn fetch_ohttp_keys(
@@ -77,6 +85,17 @@ async fn fetch_ohttp_keys(
7785
.expect("Lock should not be poisoned")
7886
.set_selected_relay(selected_relay.clone());
7987

88+
// try cache for this selected relay first
89+
if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
90+
println!("using Cached keys for relay: {}", selected_relay);
91+
if !is_expired(&cached) && cached.relay_url == selected_relay {
92+
return Ok(ValidatedOhttpKeys {
93+
ohttp_keys: cached.keys,
94+
relay_url: cached.relay_url,
95+
});
96+
}
97+
}
98+
8099
let ohttp_keys = {
81100
#[cfg(feature = "_manual-tls")]
82101
{
@@ -101,8 +120,17 @@ async fn fetch_ohttp_keys(
101120
};
102121

103122
match ohttp_keys {
104-
Ok(keys) =>
105-
return Ok(ValidatedOhttpKeys { ohttp_keys: keys, relay_url: selected_relay }),
123+
Ok(keys) => {
124+
// Cache the keys if they are not already cached for this relay
125+
if read_cached_ohttp_keys(&selected_relay).is_none() {
126+
if let Err(e) = cache_ohttp_keys(&keys, &selected_relay) {
127+
tracing::debug!(
128+
"Failed to cache OHTTP keys for relay {selected_relay}: {e:?}"
129+
);
130+
}
131+
}
132+
return Ok(ValidatedOhttpKeys { ohttp_keys: keys, relay_url: selected_relay });
133+
}
106134
Err(payjoin::io::Error::UnexpectedStatusCode(e)) => {
107135
return Err(payjoin::io::Error::UnexpectedStatusCode(e).into());
108136
}
@@ -116,3 +144,49 @@ async fn fetch_ohttp_keys(
116144
}
117145
}
118146
}
147+
148+
#[derive(Serialize, Deserialize, Debug)]
149+
struct CachedOhttpKeys {
150+
keys: payjoin::OhttpKeys,
151+
relay_url: payjoin::Url,
152+
fetched_at: u64,
153+
}
154+
155+
fn get_cache_file(relay_url: &payjoin::Url) -> PathBuf {
156+
dirs::cache_dir()
157+
.unwrap()
158+
.join("payjoin-cli")
159+
.join(relay_url.host_str().unwrap())
160+
.join("ohttp-keys.json")
161+
}
162+
163+
fn read_cached_ohttp_keys(relay_url: &payjoin::Url) -> Option<CachedOhttpKeys> {
164+
let cache_file = get_cache_file(relay_url);
165+
if !cache_file.exists() {
166+
return None;
167+
}
168+
let data = fs::read_to_string(cache_file).ok().unwrap();
169+
serde_json::from_str(&data).ok()
170+
}
171+
172+
fn cache_ohttp_keys(ohttp_keys: &payjoin::OhttpKeys, relay_url: &payjoin::Url) -> Result<()> {
173+
let cached = CachedOhttpKeys {
174+
keys: ohttp_keys.clone(),
175+
relay_url: relay_url.clone(),
176+
fetched_at: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(),
177+
};
178+
179+
let serialized = serde_json::to_string(&cached)?;
180+
let path = get_cache_file(relay_url);
181+
fs::create_dir_all(path.parent().unwrap())?;
182+
fs::write(path, serialized)?;
183+
Ok(())
184+
}
185+
186+
fn is_expired(cached_keys: &CachedOhttpKeys) -> bool {
187+
let now = SystemTime::now()
188+
.duration_since(SystemTime::UNIX_EPOCH)
189+
.unwrap_or(Duration::ZERO)
190+
.as_secs();
191+
now.saturating_sub(cached_keys.fetched_at) > CACHE_DURATION.as_secs()
192+
}

payjoin-cli/tests/e2e.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ mod e2e {
6464
res
6565
}
6666

67+
fn clear_payjoin_cache() -> std::io::Result<()> {
68+
let cache_dir = dirs::cache_dir().unwrap().join("payjoin-cli");
69+
70+
if cache_dir.exists() {
71+
std::fs::remove_dir_all(cache_dir)?;
72+
}
73+
Ok(())
74+
}
75+
6776
#[cfg(feature = "v1")]
6877
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6978
async fn send_receive_payjoin_v1() -> Result<(), BoxError> {
@@ -203,6 +212,8 @@ mod e2e {
203212
use tempfile::TempDir;
204213
use tokio::process::Child;
205214

215+
clear_payjoin_cache()?;
216+
206217
type Result<T> = std::result::Result<T, BoxError>;
207218

208219
init_tracing();

0 commit comments

Comments
 (0)