diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
| commit | c54ce061d6d278cce8362d5af085808ca60c239b (patch) | |
| tree | ec967d6195d9f7ec4f061449596611afe3a0950f /tests/sync/live_sync.rs | |
| parent | e0ad39a489b3398f8208713bf728db0cb11475b0 (diff) | |
| parent | 113928aa84894ea8f65c247d9987527e792b32a9 (diff) | |
feat: announcement purgatory
Extends purgatory to hold repository announcements until git data arrives,
preventing empty repositories from being served to clients.
When an announcement is received, a bare repo is created immediately and the
announcement is held in purgatory. It is only promoted and served once a git
push confirms real content exists. If no push arrives before expiry, the bare
repo is deleted and the announcement is silently discarded.
Key behaviours:
- Soft expiry: announcements are hidden from clients but kept alive while git
pushes are in progress, reviving on successful push
- Expiry is extended when a matching state event or git push is observed
- NIP-09 deletion events remove announcements from purgatory
- Purgatory state (announcements, state events, PR events, expired set) is
persisted to disk on graceful shutdown and restored on startup, with elapsed
downtime subtracted from expiry deadlines
- Purgatory announcements drive StateOnly sync in the sync system so state
events are fetched from listed relays before promotion
- SyncLevel added to RepoSyncIndex to distinguish purgatory repos (StateOnly)
from promoted repos (Full L2+L3 sync)
Diffstat (limited to 'tests/sync/live_sync.rs')
| -rw-r--r-- | tests/sync/live_sync.rs | 119 |
1 files changed, 43 insertions, 76 deletions
diff --git a/tests/sync/live_sync.rs b/tests/sync/live_sync.rs index 8ee3119..4289004 100644 --- a/tests/sync/live_sync.rs +++ b/tests/sync/live_sync.rs | |||
| @@ -56,43 +56,24 @@ async fn test_live_sync_layer2_events() { | |||
| 56 | // 3. Create test keys | 56 | // 3. Create test keys |
| 57 | let keys = Keys::generate(); | 57 | let keys = Keys::generate(); |
| 58 | 58 | ||
| 59 | // 4. Create a repository announcement that lists BOTH relays | 59 | // 4. Create a repository announcement on both relays with git data |
| 60 | // (purgatory requires git data before announcements are accepted) | ||
| 60 | let repo_id = "test-repo-live-l2"; | 61 | let repo_id = "test-repo-live-l2"; |
| 61 | let announcement = | 62 | let domains = vec![relay_a.domain(), relay_b.domain()]; |
| 62 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); | 63 | let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); |
| 63 | 64 | ||
| 64 | println!( | 65 | let (_announcement, _git_dir_a) = |
| 65 | "Created announcement {} (kind {})", | 66 | setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; |
| 66 | announcement.id, | 67 | println!("Announcement set up on relay_a with git data"); |
| 67 | announcement.kind.as_u16() | ||
| 68 | ); | ||
| 69 | |||
| 70 | // 5. Send announcement to relay_a | ||
| 71 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 72 | .await | ||
| 73 | .expect("Failed to connect to relay_a"); | ||
| 74 | |||
| 75 | client_a | ||
| 76 | .send_event(&announcement) | ||
| 77 | .await | ||
| 78 | .expect("Failed to send announcement to relay_a"); | ||
| 79 | println!("Announcement sent to relay_a"); | ||
| 80 | |||
| 81 | // 6. Send announcement to relay_b (triggers discovery of relay_a) | ||
| 82 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | ||
| 83 | .await | ||
| 84 | .expect("Failed to connect to relay_b"); | ||
| 85 | 68 | ||
| 86 | client_b | 69 | let (_announcement_b, _git_dir_b) = |
| 87 | .send_event(&announcement) | 70 | setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; |
| 88 | .await | 71 | println!("Announcement set up on relay_b with git data (triggers discovery)"); |
| 89 | .expect("Failed to send announcement to relay_b"); | ||
| 90 | println!("Announcement sent to relay_b (triggers discovery)"); | ||
| 91 | 72 | ||
| 92 | // 7. Wait for discovery to complete | 73 | // 5. Wait for discovery to complete |
| 93 | tokio::time::sleep(Duration::from_secs(1)).await; | 74 | tokio::time::sleep(Duration::from_secs(1)).await; |
| 94 | 75 | ||
| 95 | // 8. Create and send a Layer 2 issue event (using helper) | 76 | // 6. Create and send a Layer 2 issue event (using helper) |
| 96 | let repo_coordinate = repo_coord(&keys, repo_id); | 77 | let repo_coordinate = repo_coord(&keys, repo_id); |
| 97 | let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync") | 78 | let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync") |
| 98 | .expect("Failed to create issue event"); | 79 | .expect("Failed to create issue event"); |
| @@ -104,6 +85,10 @@ async fn test_live_sync_layer2_events() { | |||
| 104 | } | 85 | } |
| 105 | 86 | ||
| 106 | // Send issue to relay_a only | 87 | // Send issue to relay_a only |
| 88 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 89 | .await | ||
| 90 | .expect("Failed to connect to relay_a"); | ||
| 91 | |||
| 107 | client_a | 92 | client_a |
| 108 | .send_event(&issue) | 93 | .send_event(&issue) |
| 109 | .await | 94 | .await |
| @@ -111,7 +96,6 @@ async fn test_live_sync_layer2_events() { | |||
| 111 | println!("Issue sent to relay_a"); | 96 | println!("Issue sent to relay_a"); |
| 112 | 97 | ||
| 113 | client_a.disconnect().await; | 98 | client_a.disconnect().await; |
| 114 | client_b.disconnect().await; | ||
| 115 | 99 | ||
| 116 | // 9. Wait and verify event syncs to relay_b | 100 | // 9. Wait and verify event syncs to relay_b |
| 117 | let filter = Filter::new() | 101 | let filter = Filter::new() |
| @@ -166,30 +150,19 @@ async fn test_live_sync_layer3_events() { | |||
| 166 | 150 | ||
| 167 | let keys = Keys::generate(); | 151 | let keys = Keys::generate(); |
| 168 | 152 | ||
| 169 | // 2. Create and send repository announcement to both relays | 153 | // 2. Create and send repository announcement to both relays with git data |
| 154 | // (purgatory requires git data before announcements are accepted) | ||
| 170 | let repo_id = "test-repo-live-l3"; | 155 | let repo_id = "test-repo-live-l3"; |
| 171 | let announcement = | 156 | let domains = vec![relay_a.domain(), relay_b.domain()]; |
| 172 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); | 157 | let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); |
| 173 | 158 | ||
| 174 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 159 | let (_announcement, _git_dir_a) = |
| 175 | .await | 160 | setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; |
| 176 | .expect("Failed to connect to relay_a"); | 161 | println!("Announcement set up on relay_a with git data"); |
| 177 | 162 | ||
| 178 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | 163 | let (_announcement_b, _git_dir_b) = |
| 179 | .await | 164 | setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; |
| 180 | .expect("Failed to connect to relay_b"); | 165 | println!("Announcement set up on relay_b with git data (triggers discovery)"); |
| 181 | |||
| 182 | client_a | ||
| 183 | .send_event(&announcement) | ||
| 184 | .await | ||
| 185 | .expect("Failed to send announcement to relay_a"); | ||
| 186 | println!("Announcement sent to relay_a"); | ||
| 187 | |||
| 188 | client_b | ||
| 189 | .send_event(&announcement) | ||
| 190 | .await | ||
| 191 | .expect("Failed to send announcement to relay_b"); | ||
| 192 | println!("Announcement sent to relay_b (triggers discovery)"); | ||
| 193 | 166 | ||
| 194 | // 3. Wait for discovery | 167 | // 3. Wait for discovery |
| 195 | tokio::time::sleep(Duration::from_secs(1)).await; | 168 | tokio::time::sleep(Duration::from_secs(1)).await; |
| @@ -200,6 +173,10 @@ async fn test_live_sync_layer3_events() { | |||
| 200 | .expect("Failed to create issue"); | 173 | .expect("Failed to create issue"); |
| 201 | let issue_id = issue.id; | 174 | let issue_id = issue.id; |
| 202 | 175 | ||
| 176 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 177 | .await | ||
| 178 | .expect("Failed to connect to relay_a"); | ||
| 179 | |||
| 203 | client_a | 180 | client_a |
| 204 | .send_event(&issue) | 181 | .send_event(&issue) |
| 205 | .await | 182 | .await |
| @@ -243,7 +220,6 @@ async fn test_live_sync_layer3_events() { | |||
| 243 | println!("Issue synced to relay_b: {}", issue_synced); | 220 | println!("Issue synced to relay_b: {}", issue_synced); |
| 244 | 221 | ||
| 245 | client_a.disconnect().await; | 222 | client_a.disconnect().await; |
| 246 | client_b.disconnect().await; | ||
| 247 | 223 | ||
| 248 | // 7. Wait and verify comment syncs to relay_b | 224 | // 7. Wait and verify comment syncs to relay_b |
| 249 | let comment_filter = Filter::new() | 225 | let comment_filter = Filter::new() |
| @@ -343,29 +319,17 @@ async fn test_live_sync_event_ordering() { | |||
| 343 | 319 | ||
| 344 | let keys = Keys::generate(); | 320 | let keys = Keys::generate(); |
| 345 | 321 | ||
| 346 | // 2. Create and send repository announcement to both relays | 322 | // 2. Create and send repository announcement to both relays with git data |
| 323 | // (purgatory requires git data before announcements are accepted) | ||
| 347 | let repo_id = "test-repo-ordering"; | 324 | let repo_id = "test-repo-ordering"; |
| 348 | let announcement = | 325 | let domains = vec![relay_a.domain(), relay_b.domain()]; |
| 349 | create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); | 326 | let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); |
| 350 | 327 | ||
| 351 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 328 | let (_announcement, _git_dir_a) = |
| 352 | .await | 329 | setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; |
| 353 | .expect("Failed to connect to relay_a"); | 330 | let (_announcement_b, _git_dir_b) = |
| 354 | 331 | setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; | |
| 355 | let client_b = TestClient::new(relay_b.url(), keys.clone()) | 332 | println!("Announcements set up on both relays with git data"); |
| 356 | .await | ||
| 357 | .expect("Failed to connect to relay_b"); | ||
| 358 | |||
| 359 | client_a | ||
| 360 | .send_event(&announcement) | ||
| 361 | .await | ||
| 362 | .expect("Failed to send announcement to relay_a"); | ||
| 363 | |||
| 364 | client_b | ||
| 365 | .send_event(&announcement) | ||
| 366 | .await | ||
| 367 | .expect("Failed to send announcement to relay_b"); | ||
| 368 | println!("Announcements sent to both relays"); | ||
| 369 | 333 | ||
| 370 | // 3. Wait for discovery | 334 | // 3. Wait for discovery |
| 371 | tokio::time::sleep(Duration::from_secs(1)).await; | 335 | tokio::time::sleep(Duration::from_secs(1)).await; |
| @@ -375,6 +339,10 @@ async fn test_live_sync_event_ordering() { | |||
| 375 | let mut issue_ids = Vec::new(); | 339 | let mut issue_ids = Vec::new(); |
| 376 | let mut expected_order_timestamps = Vec::new(); | 340 | let mut expected_order_timestamps = Vec::new(); |
| 377 | 341 | ||
| 342 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 343 | .await | ||
| 344 | .expect("Failed to connect to relay_a"); | ||
| 345 | |||
| 378 | for i in 1..=3 { | 346 | for i in 1..=3 { |
| 379 | let issue = build_layer2_issue_event( | 347 | let issue = build_layer2_issue_event( |
| 380 | &keys, | 348 | &keys, |
| @@ -402,7 +370,6 @@ async fn test_live_sync_event_ordering() { | |||
| 402 | } | 370 | } |
| 403 | 371 | ||
| 404 | client_a.disconnect().await; | 372 | client_a.disconnect().await; |
| 405 | client_b.disconnect().await; | ||
| 406 | 373 | ||
| 407 | // 5. Wait for all events to sync | 374 | // 5. Wait for all events to sync |
| 408 | tokio::time::sleep(Duration::from_secs(3)).await; | 375 | tokio::time::sleep(Duration::from_secs(3)).await; |