diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-08 00:41:02 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-08 00:41:02 +0000 |
| commit | 5833c9bdf815699838a0445f750b99b26fd4a3bd (patch) | |
| tree | bd148e548e5621872615627cdbd88ba577d072ce /src/nostr/policy | |
| parent | ac3e00a7e102d7ae341f554563646e05aed7edac (diff) | |
feat(purgatory): track expired events to prevent infinite re-sync loops
Adds expired event tracking to prevent proactive sync from repeatedly
fetching and re-adding events that expired from purgatory without
finding git data.
Key features:
- Track expired events for 7 days to prevent re-sync loops
- Distinguish synced vs user-submitted events (via socket address)
- Allow users to retry expired events (git data might now be available)
- Reject synced expired events (prevents infinite loop)
- Daily cleanup of expired event records older than 7 days
Implementation:
- Added expired_events: DashMap<EventId, Instant> to Purgatory
- Updated event_ids() to include both purgatory + expired events
- Added is_expired(), mark_expired(), cleanup_expired_events()
- Updated cleanup() to mark expired events automatically
- Added is_synced detection in WritePolicy (localhost:0 = synced)
- Policy layer checks is_synced && is_expired() before rejecting
Behavior:
- Negentropy: Filters expired events before fetching (optimal)
- REQ+EOSE: Rejects synced expired events at policy layer
- User submissions: Always allowed to retry (skip expired check)
Testing:
- Added 5 new tests for expired event tracking
- All 222 tests passing
Fixes the infinite re-sync loop where events without git data would
expire, get synced again, expire again, repeat forever.
Diffstat (limited to 'src/nostr/policy')
| -rw-r--r-- | src/nostr/policy/state.rs | 20 |
1 files changed, 19 insertions, 1 deletions
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs index 68b1e97..7bbb379 100644 --- a/src/nostr/policy/state.rs +++ b/src/nostr/policy/state.rs | |||
| @@ -43,8 +43,12 @@ impl StatePolicy { | |||
| 43 | 43 | ||
| 44 | /// Process a state event: validate and align owner repositories | 44 | /// Process a state event: validate and align owner repositories |
| 45 | /// | 45 | /// |
| 46 | /// # Arguments | ||
| 47 | /// * `event` - The state event to process | ||
| 48 | /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) | ||
| 49 | /// | ||
| 46 | /// Returns the true if git data already availale or false if added to purgatory | 50 | /// Returns the true if git data already availale or false if added to purgatory |
| 47 | pub async fn process_state_event(&self, event: &Event) -> Result<WritePolicyResult> { | 51 | pub async fn process_state_event(&self, event: &Event, is_synced: bool) -> Result<WritePolicyResult> { |
| 48 | // Parse state to get HEAD and branch info | 52 | // Parse state to get HEAD and branch info |
| 49 | let state = | 53 | let state = |
| 50 | RepositoryState::from_event(event.clone()).context("Failed to parse state event")?; | 54 | RepositoryState::from_event(event.clone()).context("Failed to parse state event")?; |
| @@ -120,6 +124,20 @@ impl StatePolicy { | |||
| 120 | // Event will be saved and broadcast by relay builder | 124 | // Event will be saved and broadcast by relay builder |
| 121 | Ok(WritePolicyResult::Accept) | 125 | Ok(WritePolicyResult::Accept) |
| 122 | } else { | 126 | } else { |
| 127 | // Only reject expired events if they're from sync (not user-submitted) | ||
| 128 | // User-submitted events should be allowed to retry in case git data became available | ||
| 129 | if is_synced && self.ctx.purgatory.is_expired(&event.id) { | ||
| 130 | tracing::debug!( | ||
| 131 | event_id = %event.id, | ||
| 132 | identifier = %state.identifier, | ||
| 133 | "State event previously expired from purgatory (synced), rejecting to prevent re-sync loop" | ||
| 134 | ); | ||
| 135 | return Ok(WritePolicyResult::Reject { | ||
| 136 | status: false, | ||
| 137 | message: "invalid: previously expired from purgatory without git data".into(), | ||
| 138 | }); | ||
| 139 | } | ||
| 140 | |||
| 123 | // If no git data - add to purgatory | 141 | // If no git data - add to purgatory |
| 124 | // (add_state automatically enqueues for background sync) | 142 | // (add_state automatically enqueues for background sync) |
| 125 | self.ctx | 143 | self.ctx |