diff options
Diffstat (limited to 'src/sync')
| -rw-r--r-- | src/sync/mod.rs | 116 |
1 files changed, 115 insertions, 1 deletions
diff --git a/src/sync/mod.rs b/src/sync/mod.rs index fe336d1..35a8afb 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs | |||
| @@ -2050,6 +2050,119 @@ impl SyncManager { | |||
| 2050 | broadcast = broadcast_success, | 2050 | broadcast = broadcast_success, |
| 2051 | "Synced event saved and broadcast" | 2051 | "Synced event saved and broadcast" |
| 2052 | ); | 2052 | ); |
| 2053 | |||
| 2054 | // GRASP-02 PR3: Invalidate and re-process maintainer announcements | ||
| 2055 | // If this is a repository announcement that lists maintainers, check if any | ||
| 2056 | // of those maintainer announcements were previously rejected and are still | ||
| 2057 | // in the hot cache. If so, re-process them immediately (they should now pass | ||
| 2058 | // validation since the owner announcement has been accepted). | ||
| 2059 | if event.kind == Kind::GitRepoAnnouncement { | ||
| 2060 | use crate::nostr::events::RepositoryAnnouncement; | ||
| 2061 | |||
| 2062 | match RepositoryAnnouncement::from_event(event.clone()) { | ||
| 2063 | Ok(announcement) => { | ||
| 2064 | if !announcement.maintainers.is_empty() { | ||
| 2065 | tracing::debug!( | ||
| 2066 | event_id = %event.id, | ||
| 2067 | identifier = %announcement.identifier, | ||
| 2068 | maintainer_count = announcement.maintainers.len(), | ||
| 2069 | "Owner announcement accepted, checking for rejected maintainer announcements" | ||
| 2070 | ); | ||
| 2071 | |||
| 2072 | // For each maintainer, invalidate and get their events | ||
| 2073 | for maintainer_hex in &announcement.maintainers { | ||
| 2074 | // Parse maintainer public key | ||
| 2075 | match PublicKey::from_hex(maintainer_hex) { | ||
| 2076 | Ok(maintainer_pubkey) => { | ||
| 2077 | let (removed, hot_events) = rejected_events_index | ||
| 2078 | .invalidate_and_get_events( | ||
| 2079 | &maintainer_pubkey, | ||
| 2080 | &announcement.identifier, | ||
| 2081 | ); | ||
| 2082 | |||
| 2083 | if removed > 0 { | ||
| 2084 | tracing::info!( | ||
| 2085 | maintainer = %maintainer_hex, | ||
| 2086 | identifier = %announcement.identifier, | ||
| 2087 | removed_from_cold_index = removed, | ||
| 2088 | hot_cache_events = hot_events.len(), | ||
| 2089 | "Invalidated rejected maintainer announcements" | ||
| 2090 | ); | ||
| 2091 | } | ||
| 2092 | |||
| 2093 | // Re-process events from hot cache immediately | ||
| 2094 | for maintainer_event in hot_events { | ||
| 2095 | tracing::info!( | ||
| 2096 | event_id = %maintainer_event.id, | ||
| 2097 | maintainer = %maintainer_hex, | ||
| 2098 | identifier = %announcement.identifier, | ||
| 2099 | "Re-processing maintainer announcement from hot cache" | ||
| 2100 | ); | ||
| 2101 | |||
| 2102 | // Recursive call to process_event_static | ||
| 2103 | // This is safe because: | ||
| 2104 | // 1. Event was removed from hot cache before this call | ||
| 2105 | // 2. Second attempt uses maintainer exception (different code path) | ||
| 2106 | // 3. If second attempt fails, stays in cold index only (no third attempt) | ||
| 2107 | // Use Box::pin to avoid infinitely sized future | ||
| 2108 | let reprocess_result = Box::pin(Self::process_event_static( | ||
| 2109 | &maintainer_event, | ||
| 2110 | relay_url, | ||
| 2111 | database, | ||
| 2112 | write_policy, | ||
| 2113 | local_relay, | ||
| 2114 | rejected_events_index, | ||
| 2115 | )) | ||
| 2116 | .await; | ||
| 2117 | |||
| 2118 | match reprocess_result { | ||
| 2119 | ProcessResult::Saved => { | ||
| 2120 | tracing::info!( | ||
| 2121 | event_id = %maintainer_event.id, | ||
| 2122 | maintainer = %maintainer_hex, | ||
| 2123 | identifier = %announcement.identifier, | ||
| 2124 | "Maintainer announcement accepted on re-processing" | ||
| 2125 | ); | ||
| 2126 | } | ||
| 2127 | ProcessResult::Duplicate => { | ||
| 2128 | tracing::debug!( | ||
| 2129 | event_id = %maintainer_event.id, | ||
| 2130 | "Maintainer announcement already exists (duplicate)" | ||
| 2131 | ); | ||
| 2132 | } | ||
| 2133 | other => { | ||
| 2134 | tracing::warn!( | ||
| 2135 | event_id = %maintainer_event.id, | ||
| 2136 | maintainer = %maintainer_hex, | ||
| 2137 | identifier = %announcement.identifier, | ||
| 2138 | result = ?other, | ||
| 2139 | "Maintainer announcement still rejected on re-processing" | ||
| 2140 | ); | ||
| 2141 | } | ||
| 2142 | } | ||
| 2143 | } | ||
| 2144 | } | ||
| 2145 | Err(e) => { | ||
| 2146 | tracing::warn!( | ||
| 2147 | maintainer_hex = %maintainer_hex, | ||
| 2148 | error = %e, | ||
| 2149 | "Invalid maintainer public key in announcement" | ||
| 2150 | ); | ||
| 2151 | } | ||
| 2152 | } | ||
| 2153 | } | ||
| 2154 | } | ||
| 2155 | } | ||
| 2156 | Err(e) => { | ||
| 2157 | tracing::warn!( | ||
| 2158 | event_id = %event.id, | ||
| 2159 | error = %e, | ||
| 2160 | "Failed to parse repository announcement for maintainer invalidation" | ||
| 2161 | ); | ||
| 2162 | } | ||
| 2163 | } | ||
| 2164 | } | ||
| 2165 | |||
| 2053 | ProcessResult::Saved | 2166 | ProcessResult::Saved |
| 2054 | } | 2167 | } |
| 2055 | WritePolicyResult::Reject { message, status } => { | 2168 | WritePolicyResult::Reject { message, status } => { |
| @@ -2082,7 +2195,8 @@ impl SyncManager { | |||
| 2082 | .and_then(|t| t.content()) | 2195 | .and_then(|t| t.content()) |
| 2083 | { | 2196 | { |
| 2084 | // Determine rejection reason based on message | 2197 | // Determine rejection reason based on message |
| 2085 | let reason = if message.contains("doesn't list this service") { | 2198 | let reason = if message.contains("doesn't list this service") |
| 2199 | || message.contains("Announcement must list service") { | ||
| 2086 | rejected_index::RejectionReason::DoesNotListService | 2200 | rejected_index::RejectionReason::DoesNotListService |
| 2087 | } else if message.contains("maintainer") { | 2201 | } else if message.contains("maintainer") { |
| 2088 | rejected_index::RejectionReason::MaintainerNotYetValid | 2202 | rejected_index::RejectionReason::MaintainerNotYetValid |