upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/sync/filters.rs138
1 files changed, 130 insertions, 8 deletions
diff --git a/src/sync/filters.rs b/src/sync/filters.rs
index c4e20e7..3592489 100644
--- a/src/sync/filters.rs
+++ b/src/sync/filters.rs
@@ -13,15 +13,15 @@ use std::collections::HashSet;
13 13
14use nostr_sdk::prelude::*; 14use nostr_sdk::prelude::*;
15 15
16/// Layer 1: Announcements filter (kinds 30617 + 30618 + 10317) 16/// Layer 1: Announcements filter (kinds 30617 + 10317)
17/// 17///
18/// Subscribed ONCE on connect - NOT included in consolidation rebuilds. 18/// Subscribed ONCE on connect - NOT included in consolidation rebuilds.
19/// Note: 30618 is ONLY synced from remote relays, not self-subscribed. 19/// Note: State events (30618) are now subscribed via identifier-based filters in Layer 2
20/// to avoid receiving state events for repositories we don't host.
20/// Note: 10317 (User Grasp List) is synced for better GRASP discovery. 21/// Note: 10317 (User Grasp List) is synced for better GRASP discovery.
21pub fn build_announcement_filter(since: Option<Timestamp>) -> Filter { 22pub fn build_announcement_filter(since: Option<Timestamp>) -> Filter {
22 let filter = Filter::new().kinds([ 23 let filter = Filter::new().kinds([
23 Kind::GitRepoAnnouncement, // Repository announcements 24 Kind::GitRepoAnnouncement, // Repository announcements
24 Kind::RepoState, // Repository state
25 Kind::GitUserGraspList, // User Grasp List 25 Kind::GitUserGraspList, // User Grasp List
26 ]); 26 ]);
27 27
@@ -31,6 +31,64 @@ pub fn build_announcement_filter(since: Option<Timestamp>) -> Filter {
31 } 31 }
32} 32}
33 33
34/// State event filters for our hosted repositories
35///
36/// Subscribes to kind 30618 state events using #d (identifier) tags.
37/// This is more efficient than Layer 1 broadcast subscription because:
38/// - Only receives state events for repos we actually host
39/// - One filter can include multiple identifiers (batched per 100)
40/// - Avoids 1000+ rejections for repos we don't care about
41///
42/// # Arguments
43/// * `repos` - Set of repo addressable refs (format: 30617:pubkey:identifier)
44/// * `since` - Optional timestamp for incremental sync
45///
46/// # Returns
47/// Vec of filters, one filter per 100-identifier chunk
48pub fn state_event_filters_for_our_repos(
49 repos: &HashSet<String>,
50 since: Option<Timestamp>,
51) -> Vec<Filter> {
52 if repos.is_empty() {
53 return vec![];
54 }
55
56 // Extract unique identifiers from addressable refs
57 let mut identifiers: HashSet<String> = HashSet::new();
58 for repo_ref in repos {
59 // Format: 30617:pubkey:identifier
60 if let Some(identifier) = repo_ref.split(':').nth(2) {
61 identifiers.insert(identifier.to_string());
62 }
63 }
64
65 if identifiers.is_empty() {
66 return vec![];
67 }
68
69 let mut filters = Vec::new();
70 let identifier_vec: Vec<_> = identifiers.iter().collect();
71
72 // Batch by 100 identifiers per filter
73 for chunk in identifier_vec.chunks(100) {
74 let mut filter = Filter::new().kind(Kind::RepoState); // kind 30618
75
76 // Add #d tags for all identifiers in this chunk
77 for identifier in chunk {
78 filter =
79 filter.custom_tag(SingleLetterTag::lowercase(Alphabet::D), identifier.as_str());
80 }
81
82 if let Some(ts) = since {
83 filter = filter.since(ts);
84 }
85
86 filters.push(filter);
87 }
88
89 filters
90}
91
34/// Layer 2: Events tagging one of our repos 92/// Layer 2: Events tagging one of our repos
35/// 93///
36/// Uses lowercase a, uppercase A, and q tags for comprehensive coverage. 94/// Uses lowercase a, uppercase A, and q tags for comprehensive coverage.
@@ -166,6 +224,11 @@ pub fn tagged_one_of_our_root_event_filters(
166/// - compute_actions for incremental subscriptions 224/// - compute_actions for incremental subscriptions
167/// - consolidation rebuilds (Layer 1 remains active) 225/// - consolidation rebuilds (Layer 1 remains active)
168/// 226///
227/// Includes:
228/// - State event filters (kind 30618 with #d tags for our repo identifiers)
229/// - Repo-tagging filters (a/A/q tags)
230/// - Root event filters (e/E/q tags)
231///
169/// # Arguments 232/// # Arguments
170/// * `repos` - Set of repo addressable refs 233/// * `repos` - Set of repo addressable refs
171/// * `root_events` - Set of root event IDs 234/// * `root_events` - Set of root event IDs
@@ -176,6 +239,7 @@ pub fn build_layer2_and_layer3_filters(
176 since: Option<Timestamp>, 239 since: Option<Timestamp>,
177) -> Vec<Filter> { 240) -> Vec<Filter> {
178 let mut filters = Vec::new(); 241 let mut filters = Vec::new();
242 filters.extend(state_event_filters_for_our_repos(repos, since));
179 filters.extend(tagged_one_of_our_repo_event_filters(repos, since)); 243 filters.extend(tagged_one_of_our_repo_event_filters(repos, since));
180 filters.extend(tagged_one_of_our_root_event_filters(root_events, since)); 244 filters.extend(tagged_one_of_our_root_event_filters(root_events, since));
181 filters 245 filters
@@ -315,8 +379,8 @@ mod tests {
315 379
316 let filters = build_layer2_and_layer3_filters(&repos, &root_events, None); 380 let filters = build_layer2_and_layer3_filters(&repos, &root_events, None);
317 381
318 // Should have 6 filters (3 for repos + 3 for root events) 382 // Should have 7 filters (1 state + 3 for repos + 3 for root events)
319 assert_eq!(filters.len(), 6); 383 assert_eq!(filters.len(), 7);
320 } 384 }
321 385
322 #[test] 386 #[test]
@@ -328,8 +392,8 @@ mod tests {
328 392
329 let filters = build_layer2_and_layer3_filters(&repos, &root_events, None); 393 let filters = build_layer2_and_layer3_filters(&repos, &root_events, None);
330 394
331 // Should have 3 filters (3 for repos only) 395 // Should have 4 filters (1 state + 3 for repos only)
332 assert_eq!(filters.len(), 3); 396 assert_eq!(filters.len(), 4);
333 } 397 }
334 398
335 #[test] 399 #[test]
@@ -356,6 +420,64 @@ mod tests {
356 let since = Timestamp::from(1700000000); 420 let since = Timestamp::from(1700000000);
357 let filters = build_layer2_and_layer3_filters(&repos, &root_events, Some(since)); 421 let filters = build_layer2_and_layer3_filters(&repos, &root_events, Some(since));
358 422
359 assert_eq!(filters.len(), 6); 423 // Should have 7 filters (1 state + 3 for repos + 3 for root events)
424 assert_eq!(filters.len(), 7);
425 }
426
427 #[test]
428 fn test_state_event_filters_empty() {
429 let repos: HashSet<String> = HashSet::new();
430 let filters = state_event_filters_for_our_repos(&repos, None);
431
432 assert!(filters.is_empty());
433 }
434
435 #[test]
436 fn test_state_event_filters_single_repo() {
437 let mut repos = HashSet::new();
438 repos.insert("30617:abc123:test-repo".to_string());
439
440 let filters = state_event_filters_for_our_repos(&repos, None);
441
442 // Should create 1 filter with kind 30618 and #d tag
443 assert_eq!(filters.len(), 1);
444 }
445
446 #[test]
447 fn test_state_event_filters_batching() {
448 let mut repos = HashSet::new();
449 for i in 0..250 {
450 repos.insert(format!("30617:pubkey{}:repo{}", i, i));
451 }
452
453 let filters = state_event_filters_for_our_repos(&repos, None);
454
455 // Should create 3 filters (250 identifiers = 100 + 100 + 50 = 3 chunks)
456 assert_eq!(filters.len(), 3);
457 }
458
459 #[test]
460 fn test_state_event_filters_deduplicates_identifiers() {
461 let mut repos = HashSet::new();
462 // Same identifier with different pubkeys
463 repos.insert("30617:pubkey1:same-repo".to_string());
464 repos.insert("30617:pubkey2:same-repo".to_string());
465 repos.insert("30617:pubkey3:same-repo".to_string());
466
467 let filters = state_event_filters_for_our_repos(&repos, None);
468
469 // Should create 1 filter with deduplicated identifier
470 assert_eq!(filters.len(), 1);
471 }
472
473 #[test]
474 fn test_state_event_filters_with_since() {
475 let mut repos = HashSet::new();
476 repos.insert("30617:abc123:test-repo".to_string());
477
478 let since = Timestamp::from(1700000000);
479 let filters = state_event_filters_for_our_repos(&repos, Some(since));
480
481 assert_eq!(filters.len(), 1);
360 } 482 }
361} 483}