Skip to content

Payjoin-cli should cache ohttp-keys for re-use#1035

Open
zealsham wants to merge 3 commits intopayjoin:masterfrom
zealsham:cache-ohttp-key
Open

Payjoin-cli should cache ohttp-keys for re-use#1035
zealsham wants to merge 3 commits intopayjoin:masterfrom
zealsham:cache-ohttp-key

Conversation

@zealsham
Copy link
Copy Markdown
Collaborator

@zealsham zealsham commented Sep 3, 2025

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 .

To do

  • it shoudnt still use cached key when the relay-url isnt even present in config or as command-line flag
  • should handle a case of non-expired key but key being rejected by relay (will be handled in different PR)
  • get rid of unwrap calls that can panic (unhandled)
  • should not use the cached keys if --ohttp-keys flag is present cli command
  • it should be possible to cache multiple ohttp-keys based on relay-url (maybe in subsequent pr ?)

Pull Request Checklist

Please confirm the following before requesting review:

@zealsham zealsham marked this pull request as draft September 3, 2025 00:10
@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Sep 3, 2025

Pull Request Test Coverage Report for Build 23672911589

Details

  • 47 of 50 (94.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 84.165%

Changes Missing Coverage Covered Lines Changed/Added Lines %
payjoin-cli/src/app/v2/ohttp.rs 47 50 94.0%
Totals Coverage Status
Change from base Build 23607944477: 0.04%
Covered Lines: 10694
Relevant Lines: 12706

💛 - Coveralls

@DanGould DanGould added payjoin-cli enhancement New feature or request labels Sep 3, 2025
@zealsham zealsham changed the title Payjoin-cli should cache ohttp-keys for re-use WIP: Payjoin-cli should cache ohttp-keys for re-use Sep 14, 2025
@zealsham zealsham changed the title WIP: Payjoin-cli should cache ohttp-keys for re-use Payjoin-cli should cache ohttp-keys for re-use Sep 24, 2025
@zealsham zealsham marked this pull request as ready for review September 24, 2025 13:47
relay_manager: Arc<Mutex<RelayManager>>,
) -> Result<ValidatedOhttpKeys> {
println!("before first some");
if let Some(ohttp_keys) = config.v2()?.ohttp_keys.clone() {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops! , println statements i used for debugging got commited accidentally , cleaning it up

@arminsabouri
Copy link
Copy Markdown
Collaborator

@zealsham I am going to mark this as draft. Do you mind cleaning up the commit messages and history and re-opening. Thank you.

@arminsabouri arminsabouri marked this pull request as draft November 24, 2025 16:35
@zealsham zealsham force-pushed the cache-ohttp-key branch 2 times, most recently from 971c8d3 to 2198e3f Compare November 25, 2025 17:31
@zealsham zealsham marked this pull request as ready for review November 25, 2025 18:40
@zealsham
Copy link
Copy Markdown
Collaborator Author

@arminsabouri the PR is ready


// try cache for this selected relay first
if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
println!("using Cached keys for relay: {selected_relay}");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the logger instead of println?


// try cache for this selected relay first
if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
println!("using Cached keys for relay: {selected_relay}");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
println!("using Cached keys for relay: {selected_relay}");
println!("using Cached keys for relay: {selected_relay}");

// try cache for this selected relay first
if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
println!("using Cached keys for relay: {selected_relay}");
if !is_expired(&cached) && cached.relay_url == selected_relay {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would read_cached_ohttp_keys return an expired or keys for a different relay? Perphaps read_cached_ohttp_keys should return ValidateOhttpKeys?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thier is a possiblity of the cached keys being expired by the time we try to use. so it checks that the keys being rend hasn't elapsed that duration before using it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the relay_url were the key the comparison would not be necessary, but instead it's just the host part of this key. i think that only causes collisions when different ports are used, see other comment

fetched_at: u64,
}

fn get_cache_file(relay_url: &url::Url) -> PathBuf {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are cached keys not being persisted in the database?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are persisted to file system and then get replaced with new keys if the cached one expires. i think it's pretty more straight forward than adding it to the db. on the file system keys are stored per-relay, each relay can have one key cache at a time

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already use sqlite so why not have a table with relay URL as the key and pubkey as the value? no need for serde, key mangling, or an additional bit of mutable storage to remember exists

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another note: keys correspond to directories, not relays. Might just be a mistype but it's a concrete distinction I felt necessary to repeat for certainty.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another note: keys correspond to directories, not relays. Might just be a mistype but it's a concrete distinction I felt necessary to repeat for certainty.

this is very important

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 .
Copy link
Copy Markdown
Contributor

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the cache TTL being hard coded to 6 months is a bit problematic. if a directory's keys are lost, this may render its URL useless for that period of time, giving operators no recourse.

instead we should probably use standard HTTP cache control headers, which would allow an operator with high confidence in their deployment to set more aggressive caching parameters.

rfc 9458 says (sections 5.2, 6.4) that a server must generate a response with a 422 status in response to an invalid message, which is what the server sees if it can't decapsulate because the client is using a stale pubkey.

i'm not sure what the implementation and/or testing for that is right now, but we should handle that by fetching the ohttp key and comparing it, erroring if there is an inconsistency but allowing opting in to just overwrite the old key with the new key (i.e. making such errors into warnings)

Comment on lines +11 to +12
// 6 months
const CACHE_DURATION: Duration = Duration::from_secs(6 * 30 * 24 * 60 * 60);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a pretty long duration to hard code. it seems more appropriate to allow OHTTP gateways to control this by using the cache control mechanisms defined in HTTP, so that operators can determine their own policies?

dirs::cache_dir()
.unwrap()
.join("payjoin-cli")
.join(relay_url.host_str().unwrap())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not store relay_url in full so two gateways on the same host don't collide?

the only collision that can happen in practice given that we set things up to be on the top level of a single vhost, is the same host with different ports

return Ok(ValidatedOhttpKeys { ohttp_keys: keys, relay_url: selected_relay }),
Ok(keys) => {
// Cache the keys if they are not already cached for this relay
if read_cached_ohttp_keys(&selected_relay).is_none() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this reads from the cache a second time unnecessarily

// try cache for this selected relay first
if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
println!("using Cached keys for relay: {selected_relay}");
if !is_expired(&cached) && cached.relay_url == selected_relay {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the relay_url were the key the comparison would not be necessary, but instead it's just the host part of this key. i think that only causes collisions when different ports are used, see other comment

if let Some(cached) = read_cached_ohttp_keys(&selected_relay) {
tracing::info!("using Cached keys for relay: {selected_relay}");
if !is_expired(&cached) && cached.relay_url == selected_relay {
return Ok(ValidatedOhttpKeys {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning short circuits here which removes the need to read from the cache a second time for writeback

@nothingmuch
Copy link
Copy Markdown
Contributor

see also the key consistency reference of rfc 9458 which discusses strategies for shared caching, which i think we can include in the mailroom services... that might be sufficient motivation to not use a reqwest middleware for caching.

@nothingmuch
Copy link
Copy Markdown
Contributor

as Dan pointed out, this should be keyed the directory url, not relay url, everywhere i wrote relay_url i was confused

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request payjoin-cli

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants