tangle


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

commit 1c07a5b4b34147700921cf2a2bb111b38ed0f4a5
parent 7ce1c9252c230aa6b256d1c2833d5b9ee1842672
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 04:41:16 -0700

runtime: centralize group read gate

- Replace separate Tangle and Pocket visibility helpers with one group_read_gate_visible_to_auth path.
- Route offset lookup, store query screening, direct fanout, and offset fanout through the shared read gate helper.
- Activate the Phase 2 target that guards REQ COUNT and live fanout against divergent group read-gate paths.
- Validated with cargo fmt --all -- --check, cargo check --workspace --all-targets, cargo test --workspace, and cargo clippy --workspace --all-targets -- -D warnings.

Diffstat:
Mcrates/tangle_runtime/src/relay/core.rs | 44++++++++++++++++----------------------------
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 18++++++++++++++++--
2 files changed, 32 insertions(+), 30 deletions(-)

diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -11,11 +11,11 @@ use crate::relay::{ use std::{cell::RefCell, collections::BTreeSet}; use tangle_crypto::verify_event_signature; use tangle_groups::{ - GroupAuthContext, GroupEventClass, GroupProjection, GroupRuntimeConfig, StoreOffset, - validate_client_group_event_structure, + GroupAuthContext, GroupEventClass, GroupEventView, GroupProjection, GroupRuntimeConfig, + StoreOffset, validate_client_group_event_structure, }; use tangle_protocol::{ClientMessage, Event, Filter, RelayMessage, SubscriptionId, UnixTimestamp}; -use tangle_store_pocket::{PocketEvent, PocketScreenResult, PocketStoreConfig, PocketStoreHandle}; +use tangle_store_pocket::{PocketScreenResult, PocketStoreConfig, PocketStoreHandle}; pub struct BaseRelay { store: PocketStoreHandle, @@ -160,7 +160,8 @@ impl BaseRelay { auth: &BaseAuthState, ) -> Result<Option<Event>, BaseRelayError> { let event = self.event_by_offset(offset)?; - if self.event_visible_to_auth( + if Self::group_read_gate_visible_to_auth( + self.groups.as_ref(), &event, &GroupAuthContext::new(auth.authenticated_pubkeys().iter().cloned()), )? { @@ -415,9 +416,7 @@ impl BaseRelay { pub fn fanout(&mut self, event: &Event) -> Vec<RelayMessage> { let groups = self.groups.as_ref(); self.subscriptions.fanout(event, |event, auth| { - groups - .map(|groups| groups.event_visible_to_auth(event, auth).unwrap_or(false)) - .unwrap_or(true) + Self::group_read_gate_visible_to_auth(groups, event, auth).unwrap_or(false) }) } @@ -479,7 +478,11 @@ impl BaseRelay { } match pocket_filter.event_matches(pocket_event) { Ok(false) => PocketScreenResult::Mismatch, - Ok(true) => match self.pocket_event_visible_to_auth(pocket_event, auth) { + Ok(true) => match Self::group_read_gate_visible_to_auth( + self.groups.as_ref(), + pocket_event, + auth, + ) { Ok(true) => PocketScreenResult::Match, Ok(false) => PocketScreenResult::Redacted, Err(error) => { @@ -519,25 +522,12 @@ impl BaseRelay { .collect() } - fn event_visible_to_auth( - &self, - event: &Event, + fn group_read_gate_visible_to_auth( + groups: Option<&GroupService>, + event: &(impl GroupEventView + ?Sized), auth: &GroupAuthContext, ) -> Result<bool, BaseRelayError> { - self.groups - .as_ref() - .map(|groups| groups.event_visible_to_auth(event, auth)) - .unwrap_or(Ok(true)) - .map_err(BaseRelayError::from) - } - - fn pocket_event_visible_to_auth( - &self, - event: &PocketEvent, - auth: &GroupAuthContext, - ) -> Result<bool, BaseRelayError> { - self.groups - .as_ref() + groups .map(|groups| groups.event_visible_to_auth(event, auth)) .unwrap_or(Ok(true)) .map_err(BaseRelayError::from) @@ -551,9 +541,7 @@ impl BaseRelay { let event = self.event_by_offset(offset)?; let groups = self.groups.as_ref(); Ok(subscriptions.fanout(&event, |event, auth| { - groups - .map(|groups| groups.event_visible_to_auth(event, auth).unwrap_or(false)) - .unwrap_or(true) + Self::group_read_gate_visible_to_auth(groups, event, auth).unwrap_or(false) })) } } diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -480,9 +480,23 @@ fn closed_groups_use_strict_nip29_semantics_without_compatibility_flag() { } #[test] -#[ignore = "phase2 target: central read gate"] fn req_count_and_live_fanout_share_one_group_read_gate() { - pending("REQ COUNT and live fanout must use one central group read gate"); + let relay_core = include_str!("../src/relay/core.rs"); + + assert_eq!( + relay_core + .matches("fn group_read_gate_visible_to_auth") + .count(), + 1 + ); + assert_eq!( + relay_core + .matches("Self::group_read_gate_visible_to_auth") + .count(), + 4 + ); + assert!(!relay_core.contains("fn event_visible_to_auth(")); + assert!(!relay_core.contains("fn pocket_event_visible_to_auth(")); } #[test]