Skip to content

fix(relay): stop overcounting NIP-45 COUNT for non-pushable filter constraints - #3437

Closed
1nderboi wants to merge 1 commit into
block:mainfrom
1nderboi:fix/3373-count-pushable
Closed

fix(relay): stop overcounting NIP-45 COUNT for non-pushable filter constraints#3437
1nderboi wants to merge 1 commit into
block:mainfrom
1nderboi:fix/3373-count-pushable

Conversation

@1nderboi

Copy link
Copy Markdown

Summary

Fixes #3373. filter_fully_pushable() was letting several filter shapes through to the fast SQL COUNT path that filter_to_query_params() actually drops instead of pushing as predicates — causing COUNT to overcount:

  • A present-but-empty constraint (authors: [], ids: [], or any generic tag with zero values) matches nothing under filters_match, but the query builder just omits it rather than emitting a match-nothing predicate.
  • A single #h value that isn't a channel UUID (e.g. a bare NIP-29 group id) was treated as pushable, but only UUID-valued #h is pushed as channel_id — a non-UUID value needs post-filtering since events can legitimately carry it as a literal tag.
  • Any other generic tag (#t, #a, etc.) with a non-empty value list was allowed through when it should always require post-filtering.

All three now correctly fall back to the exact post-filtered COUNT path.

Changes

  • crates/buzz-relay/src/handlers/req.rs: reject empty authors/ids/generic-tag constraints, reject non-UUID single #h, reject any non-empty non-h/p/d/e generic tag.
  • 6 new unit tests covering: UUID vs non-UUID single #h, multi-#h, empty authors/ids/generic-tag constraints, non-empty #t, and the plain kind+author baseline that should stay pushable.

Test plan

  • cargo fmt -p buzz-relay --check — clean
  • Manual trace of filter_fully_pushable against filters_match / filter_to_query_params for each new case
  • cargo test -p buzz-relaycould not run to completion locally. This shared dev machine's disk repeatedly hit ENOSPC mid-build (down to 0 bytes free at one point) even after cargo clean-ing target dirs; the buzz-relay dependency tree (aws-lc-sys, sqlx-postgres, opentelemetry_sdk, mesh-llm-native-runtime) alone exceeds available local headroom. Requesting CI run the full suite including the new tests in req.rs's mod tests.

🤖 Generated with Claude Code

…nstraints

filter_fully_pushable() let several constraints through that filter_to_query_params()
silently drops instead of pushing as SQL predicates, causing the fast COUNT path to
overcount:

- A present-but-empty constraint (authors: [], ids: [], or any generic tag with zero
  values) matches nothing under filters_match, but the query builder just omits it
  rather than emitting a match-nothing predicate.
- A single #h value that isn't a channel UUID (e.g. a bare NIP-29 group id) was
  treated as pushable, but only UUID-valued #h is pushed as channel_id — a non-UUID
  value needs post-filtering since events can legitimately carry it as a literal tag.
- Any other generic tag (#t, #a, etc.) with a non-empty value list was allowed through
  when it should always require post-filtering.

Reject all of these so COUNT falls back to the exact post-filtered path instead of
returning an inflated count.

Added unit tests for: UUID vs non-UUID single #h, multi-#h, empty authors/ids/generic-tag
constraints, non-empty #t, and the plain kind+author baseline that should stay pushable.

Note: could not run `cargo test`/`cargo check` to completion locally — this machine's
shared disk repeatedly hit ENOSPC mid-build (0 bytes free at one point) even after
cleaning target/ dirs; the buzz-relay dependency tree alone exceeds available headroom.
Verified via cargo fmt --check (clean) and manual trace of filter_fully_pushable against
filters_match/filter_to_query_params. Please have CI confirm cargo test passes.

Signed-off-by: 1nderboi <201919958+1nderboi@users.noreply.github.com>
@1nderboi
1nderboi requested a review from a team as a code owner July 29, 2026 00:16
@1nderboi

Copy link
Copy Markdown
Author

Closing in favor of #3374, which fixes the identical bug (filter_fully_pushable letting a non-UUID single #h and any present-but-empty constraint through to the fast COUNT path) the same way, was opened first, and additionally has an e2e wire-level regression test against a real relay (test_count_unpushable_filters_do_not_count_all_channels) on top of unit tests — stronger coverage than this PR's unit-tests-only approach.

For the record: locally confirmed this branch's fix works too — cargo test -p buzz-relay --lib req:: → 51 passed, 0 failed — but no need for two competing fixes on the same issue. Thanks @beardthelion for #3374.

@1nderboi 1nderboi closed this Jul 29, 2026
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.

relay: NIP-45 COUNT returns an accessible-channel-wide total when a filter's constraint never reaches SQL

1 participant