upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 21:12:27 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 21:12:27 +0000
commita94fbc1f616128c93539c71b003495e5f6291c69 (patch)
tree42bd4a38a6b4a122c2cd4c365c509237a402e6bb
parent01aeb2a3265bcafa162987c85dd281981770bba7 (diff)
feat: request kind-5 deletions for state/announcement events by #e
Add a non_proposal_event_ids parameter to get_fetch_filters and a corresponding field on FetchRequest. On each fetch, the event IDs of cached repo announcements and the state event are collected and used to build a dedicated kind-5 filter keyed on #e tags, as specified by NIP-09. The existing #a-tagged filter already covers addressable-event deletions; this new filter catches deletions from clients that follow NIP-09 strictly and do not embed a repo coordinate in their deletion event.
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/lib/client.rs46
2 files changed, 45 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4b8083..1c5b583 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11 11
12- git server push option passthrough, enabling `-o secret-scanning.skip` for grasp servers 12- git server push option passthrough, enabling `-o secret-scanning.skip` for grasp servers
13- `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay` 13- `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay`
14- Fetch filters now request kind-5 deletion events for cached state and repo announcement events by `#e` tag (NIP-09), in addition to the existing `#a`-tagged filter; ensures deletions of these events are received even from clients that do not embed a repo coordinate in their deletion event
14 15
15### Fixed 16### Fixed
16 17
diff --git a/src/lib/client.rs b/src/lib/client.rs
index e657f53..95dd78d 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -798,6 +798,9 @@ impl Connect for Client {
798 ) 798 )
799 .copied() 799 .copied()
800 .collect(); 800 .collect();
801 // Only request non-proposal event deletions on the first loop iteration;
802 // cleared after first use so subsequent iterations don't re-request them.
803 let mut fresh_non_proposal_event_ids = request.non_proposal_event_ids.clone();
801 804
802 let mut report = FetchReport::default(); 805 let mut report = FetchReport::default();
803 806
@@ -813,8 +816,13 @@ impl Connect for Client {
813 let dim = Style::new().color256(247); 816 let dim = Style::new().color256(247);
814 817
815 loop { 818 loop {
816 let filters = 819 let filters = get_fetch_filters(
817 get_fetch_filters(&fresh_coordinates, &fresh_proposal_roots, &fresh_profiles); 820 &fresh_coordinates,
821 &fresh_proposal_roots,
822 &fresh_non_proposal_event_ids,
823 &fresh_profiles,
824 );
825 fresh_non_proposal_event_ids = HashSet::new();
818 826
819 if let Some(pb) = &pb { 827 if let Some(pb) = &pb {
820 pb.set_prefix( 828 pb.set_prefix(
@@ -1727,6 +1735,8 @@ async fn create_relays_request(
1727 for filter in get_fetch_filters( 1735 for filter in get_fetch_filters(
1728 &repo_coordinates_without_relays, 1736 &repo_coordinates_without_relays,
1729 &proposals, 1737 &proposals,
1738 &HashSet::new(), /* non_proposal_event_ids not yet computed; deletion events are not
1739 * cached locally */
1730 &missing_contributor_profiles 1740 &missing_contributor_profiles
1731 .union( 1741 .union(
1732 &profiles_to_fetch_from_user_relays 1742 &profiles_to_fetch_from_user_relays
@@ -1828,6 +1838,21 @@ async fn create_relays_request(
1828 } else { 1838 } else {
1829 None 1839 None
1830 }, 1840 },
1841 non_proposal_event_ids: {
1842 let mut ids: HashSet<EventId> = HashSet::new();
1843 // Include repo announcement event IDs so we can request kind-5
1844 // deletions for them by #e tag (NIP-09 style).
1845 if let Some(repo_ref) = &repo_ref {
1846 for event in repo_ref.events.values() {
1847 ids.insert(event.id);
1848 }
1849 // Also include the state event ID if we have one.
1850 if let Ok(existing_state) = get_state_from_cache(git_repo_path, repo_ref).await {
1851 ids.insert(existing_state.event.id);
1852 }
1853 }
1854 ids
1855 },
1831 proposals, 1856 proposals,
1832 contributors, 1857 contributors,
1833 missing_contributor_profiles, 1858 missing_contributor_profiles,
@@ -2075,6 +2100,7 @@ pub fn consolidate_fetch_reports(reports: Vec<Result<FetchReport>>) -> FetchRepo
2075pub fn get_fetch_filters( 2100pub fn get_fetch_filters(
2076 repo_coordinates: &HashSet<Nip19Coordinate>, 2101 repo_coordinates: &HashSet<Nip19Coordinate>,
2077 proposal_ids: &HashSet<EventId>, 2102 proposal_ids: &HashSet<EventId>,
2103 non_proposal_event_ids: &HashSet<EventId>,
2078 required_profiles: &HashSet<PublicKey>, 2104 required_profiles: &HashSet<PublicKey>,
2079) -> Vec<nostr::Filter> { 2105) -> Vec<nostr::Filter> {
2080 [ 2106 [
@@ -2124,6 +2150,19 @@ pub fn get_fetch_filters(
2124 ), 2150 ),
2125 ] 2151 ]
2126 }, 2152 },
2153 // Request kind-5 deletions for state events and repo announcements by
2154 // their event ID (#e tag), as per NIP-09. The #a-tagged filter above
2155 // covers addressable-event deletions; this covers the specific event IDs
2156 // of the state and announcement events we already have cached.
2157 if non_proposal_event_ids.is_empty() {
2158 vec![]
2159 } else {
2160 vec![
2161 nostr::Filter::default()
2162 .kind(Kind::EventDeletion)
2163 .events(non_proposal_event_ids.clone()),
2164 ]
2165 },
2127 if required_profiles.is_empty() { 2166 if required_profiles.is_empty() {
2128 vec![] 2167 vec![]
2129 } else { 2168 } else {
@@ -2290,6 +2329,9 @@ pub struct FetchRequest {
2290 repo_coordinates_without_relays: Vec<(Nip19Coordinate, Option<Timestamp>)>, 2329 repo_coordinates_without_relays: Vec<(Nip19Coordinate, Option<Timestamp>)>,
2291 state: Option<(Timestamp, EventId)>, 2330 state: Option<(Timestamp, EventId)>,
2292 proposals: HashSet<EventId>, 2331 proposals: HashSet<EventId>,
2332 /// Event IDs of non-proposal events (state events, repo announcements) for
2333 /// which we should also request kind-5 deletion events by `#e` tag.
2334 non_proposal_event_ids: HashSet<EventId>,
2293 contributors: HashSet<PublicKey>, 2335 contributors: HashSet<PublicKey>,
2294 missing_contributor_profiles: HashSet<PublicKey>, 2336 missing_contributor_profiles: HashSet<PublicKey>,
2295 existing_events: HashSet<EventId>, 2337 existing_events: HashSet<EventId>,