1+ use std:: fs;
2+ use std:: path:: PathBuf ;
13use std:: sync:: { Arc , Mutex } ;
4+ use std:: time:: { Duration , SystemTime } ;
25
36use anyhow:: { anyhow, Result } ;
7+ use serde:: { Deserialize , Serialize } ;
48
59use super :: Config ;
610
11+ // 6 months
12+ const CACHE_DURATION : Duration = Duration :: from_secs ( 6 * 30 * 24 * 60 * 60 ) ;
13+
714#[ derive( Debug , Clone ) ]
815pub 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
4957async 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+ }
0 commit comments