tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit b9cb95abfc26d965edd7785ecf9be69a1ba387a9
parent 4f8ba91c3bdf29e5fcc3f6b8621ef5085623bef6
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 03:21:29 -0700

runtime: count visible query results exactly

- count COUNT filters through the central visible query screen instead of REQ result length
- add an unbounded filter clone so COUNT ignores initial-query limit caps
- dedupe overlapping filter matches by event id before returning exact counts
- cover visible COUNT dedupe, unbounded limit behavior, and filter helper semantics

Diffstat:
Mcrates/tangle_protocol/src/lib.rs | 10++++++++++
Mcrates/tangle_runtime/src/relay/core.rs | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/crates/tangle_protocol/src/lib.rs b/crates/tangle_protocol/src/lib.rs @@ -686,6 +686,12 @@ impl Filter { self.limit } + pub fn without_limit(&self) -> Self { + let mut filter = self.clone(); + filter.limit = None; + filter + } + pub fn search(&self) -> Option<&str> { self.search.as_deref() } @@ -1889,6 +1895,10 @@ mod tests { assert!(Filter::empty().matches(&event)); assert_eq!(Filter::empty().limit(), None); assert_eq!(Filter::empty().search(), None); + let without_limit = filter.without_limit(); + assert_eq!(without_limit.limit(), None); + assert_eq!(without_limit.search(), filter.search()); + assert!(without_limit.matches(&event)); assert_eq!( filter_from_value(&filter_to_value(&filter)).expect("encoded"), filter diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -347,7 +347,7 @@ impl BaseRelay { ) -> Result<RelayMessage, BaseRelayError> { Ok(RelayMessage::Count { subscription_id, - count: self.query_events(&filters, auth)?.len() as u64, + count: self.count_events(&filters, auth)?, }) } @@ -389,6 +389,21 @@ impl BaseRelay { Ok(Self::sort_and_dedupe_query_events(output)) } + fn count_events( + &self, + filters: &[Filter], + auth: &GroupAuthContext, + ) -> Result<u64, BaseRelayError> { + let mut seen = BTreeSet::new(); + for filter in filters { + let filter = filter.without_limit(); + for event in self.query_filter_events(&filter, auth)? { + seen.insert(event.id().clone()); + } + } + u64::try_from(seen.len()).map_err(|_| BaseRelayError::error("visible event count overflow")) + } + fn query_filter_events( &self, filter: &Filter, @@ -607,6 +622,58 @@ mod tests { } #[test] + fn base_relay_count_dedupes_overlapping_visible_filters() { + let mut relay = test_relay("base-relay-count-dedupe", 8); + let market_tag = Tag::from_parts("t", &["market"]).expect("tag"); + let first = signed_event_at(7, 1, vec![market_tag.clone()], "first", 1_714_124_433); + let second = signed_event_at(8, 1, vec![market_tag], "second", 1_714_124_434); + let third = signed_event_at(7, 2, Vec::new(), "third", 1_714_124_435); + + for event in [&first, &second, &third] { + assert_accepted(relay.handle_event(event.clone()).expect("event"), event); + } + + let market_notes = + filter_from_value(&serde_json::json!({"kinds":[1],"#t":["market"],"limit":2})) + .expect("market filter"); + let author_events = filter_from_value(&serde_json::json!({ + "authors":[first.unsigned().pubkey().as_str()], + "kinds":[1,2], + "limit":10 + })) + .expect("author filter"); + let limited_market = + filter_from_value(&serde_json::json!({"kinds":[1],"#t":["market"],"limit":1})) + .expect("limited filter"); + + assert_eq!( + relay + .handle_count( + SubscriptionId::new("count-limit").expect("sub"), + vec![limited_market] + ) + .expect("count"), + RelayMessage::Count { + subscription_id: SubscriptionId::new("count-limit").expect("sub"), + count: 2 + } + ); + + assert_eq!( + relay + .handle_count( + SubscriptionId::new("count-dedupe").expect("sub"), + vec![market_notes, author_events] + ) + .expect("count"), + RelayMessage::Count { + subscription_id: SubscriptionId::new("count-dedupe").expect("sub"), + count: 3 + } + ); + } + + #[test] fn base_relay_rejects_group_marked_events_before_group_service() { let mut relay = test_relay("base-relay-group-reject", 4); let event = signed_public_event(