Skip to content

fix(mailroom): reject post requests over capacity#1509

Merged
spacebear21 merged 7 commits into
payjoin:masterfrom
va-an:fix/post-capacity-check
May 4, 2026
Merged

fix(mailroom): reject post requests over capacity#1509
spacebear21 merged 7 commits into
payjoin:masterfrom
va-an:fix/post-capacity-check

Conversation

@va-an

@va-an va-an commented Apr 28, 2026

Copy link
Copy Markdown

Closes #1507

Both post_v2 and post_v1_req_and_wait grow the in-memory counters tracked by len(), but only wait_v2 was guarded by has_capacity(). This patch applies the same check to the two write paths so the capacity bound is enforced symmetrically.

Pull Request Checklist

Please confirm the following before requesting review:

Both `post_v2` and `post_v1_req_and_wait` grow the in-memory
counters tracked by `len()`, but only `wait_v2` was guarded by
`has_capacity()`. Apply the same check to the two write paths
so the capacity bound is enforced symmetrically.

@nothingmuch nothingmuch left a comment

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.

utACK, not sure how I missed this (when i wrote it)

i would be happier if this included regression tests similar to the existing capacity tests

id: &ShortId,
payload: Vec<u8>,
) -> Result<Option<oneshot::Receiver<Vec<u8>>>, Error> {
if !self.has_capacity().await? {

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.

note that technically in this fn it could be that the v2 removal makes room for the v1 insert.

i think it's better this way since the code is simpler, but technically it implies v1 writers will in some cases see effective capacity of n-1 instead of n

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That's not obvious, thanks for pointing it out.
I'll come back to this when I understand the code better.

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.

IMO don't bother fixing this, maybe just add a comment to indicate that this edge case exists

Add regression tests covering `OverCapacity` rejection from each
DB method that the capacity check guards: `post_v2_payload`,
`post_v1_request_and_wait_for_response`, and `wait_for_v2_payload`.

Long-poll tests spawn parallel waiters and poll `Mailboxes::len`
until `pending_v1`/`pending_v2` are full before probing.

Extract `random_id` and `random_payload` helpers shared across
the three new tests.
@va-an

va-an commented Apr 29, 2026

Copy link
Copy Markdown
Author

i would be happier if this included regression tests similar to the existing capacity tests

Thanks for the review.
Couldn't find existing capacity tests to mirror, so I added one per guarded DB method.

@va-an
va-an requested a review from nothingmuch April 29, 2026 09:03
Comment on lines +950 to +954

fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) }

fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() }

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.

When a ran using cargo run The compiler returns two warnings about these functions being unused. Since they are only used in tests, you can use #[cfg(test)]

Suggested change
fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) }
fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() }
#[cfg(test)]
fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) }
#[cfg(test)]
fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() }

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.

in the rest of the codebase when we have this kind of stuff we usually add

#[cfg(test)]
mod tests {
    ...
}

around all the tests to reduce the boilerplate, i can't decide if it's worth it in this case

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.

Agreed, wrapping everything in a #[cfg(test)] mod tests { } block is cleaner and more consistent with the rest of the codebase. It avoids annotating each helper individually and makes it clear at a glance that all of it is test-only code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I would prefer to wrap tests in files.rs to mod tests {} for consistency with codebase, even we decided to delete this helper functions below.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@nothingmuch should I add commit hash d8afeaa to .git-blame-ignore-revs file? And if so - what's the accepted way to do this: a commit in this PR, a separate PR, etc?

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.

should I add commit hash d8afeaa to .git-blame-ignore-revs file?

no need IMO, git blame has -w option, ignore-revs is more useful for massive reformatting that isn't trivially addressed by git's move/copy detection or -w

And if so - what's the accepted way to do this: a commit in this PR, a separate PR, etc?

in general, just another commit in the PR makes the most sense to me, it's impossible to do it in one commit but that's the closest thing to that

@nothingmuch

Copy link
Copy Markdown
Contributor

Thanks for the review. Couldn't find existing capacity tests to mirror, so I added one per guarded DB method.

test_prune is the test i had in mind FWIW

Comment thread payjoin-mailroom/src/db/files.rs Outdated
Comment on lines +951 to +953
fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) }

fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() }

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.

i think these functions can be removed, you can just use the loop index as the id

Comment thread payjoin-mailroom/src/db/files.rs Outdated
Comment on lines +972 to +975
assert!(matches!(
db.post_v2_payload(&random_id(), random_payload()).await,
Err(DbError::OverCapacity)
));

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.

all mailboxes share the same capacity, so i think it'd be more correct to assert that whenever there's no capacity, this is true of all request types:

Suggested change
assert!(matches!(
db.post_v2_payload(&random_id(), random_payload()).await,
Err(DbError::OverCapacity)
));
assert!(matches!(
db.post_v2_payload(&random_id(), random_payload()).await,
Err(DbError::OverCapacity)
));
assert!(matches!(db.wait_for_v2_payload(&random_id()).await, Err(DbError::OverCapacity)));
assert!(matches!(
db.post_v1_request_and_wait_for_response(&random_id(), random_payload()).await,
Err(DbError::OverCapacity)
));

same for the lower checks (so this can be a utility function, or even just a single test which always asserts this for a variety of different at-capacity setups)

va-an added 4 commits April 30, 2026 13:39
Move existing tests under a `mod tests {}` submodule.
No functional changes.
Drop the `random_id` and `random_payload` helpers in favor of
deterministic `ShortId([i; 8])` and a fixed payload.
Capacity is shared across all request types, so once it is full
every path must reject with `OverCapacity` regardless of how it
was filled. Extract the assertion into a helper and call it
from each capacity test.

@nothingmuch nothingmuch left a comment

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.

utACK

@caarloshenriq caarloshenriq left a comment

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.

tACK 48cc7c7

@spacebear21 spacebear21 left a comment

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.

ACK

@spacebear21

Copy link
Copy Markdown
Collaborator

@va-an sorry for the delay in running the CI workflows, can you fix the lint errors before we merge?

@va-an

va-an commented May 4, 2026

Copy link
Copy Markdown
Author

@va-an sorry for the delay in running the CI workflows, can you fix the lint errors before we merge?

Done - 24840c5. These lint errors didn't show up locally under devenv.
Want me to squash into a single commit before merging?

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 25330149521

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.1%) to 85.088%

Details

  • Coverage increased (+0.1%) from the base build.
  • Patch coverage: 10 uncovered changes across 2 files (306 of 316 lines covered, 96.84%).
  • 15 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
payjoin-mailroom/src/db/files.rs 315 306 97.14%
payjoin-mailroom/src/main.rs 1 0 0.0%

Coverage Regressions

15 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
payjoin-cli/src/app/v2/ohttp.rs 15 74.24%

Coverage Stats

Coverage Status
Relevant Lines: 13526
Covered Lines: 11509
Line Coverage: 85.09%
Coverage Strength: 396.36 hits per line

💛 - Coveralls

@nothingmuch

Copy link
Copy Markdown
Contributor

These lint errors didn't show up locally under devenv. Want me to squash into a single commit before merging?

i think this is the result of the toolchain (provided by dtolnay/rust-toolchain@nightly action) bumping clippy version, it's not related to your PR at all

@spacebear21
spacebear21 self-requested a review May 4, 2026 16:53

@spacebear21 spacebear21 left a comment

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.

reACK

@spacebear21
spacebear21 merged commit e3dec5b into payjoin:master May 4, 2026
16 checks passed
@va-an
va-an deleted the fix/post-capacity-check branch May 4, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mailroom: check capacity when write to Mailboxes

5 participants