From c93ffaf30ae43cdaf1cf59684318970beca65979 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 18 Dec 2025 16:58:58 +0000 Subject: refactor: rename bootstrap.rs to historic_sync.rs for clarity --- tests/sync/bootstrap.rs | 429 -------------------------------------------- tests/sync/historic_sync.rs | 429 ++++++++++++++++++++++++++++++++++++++++++++ tests/sync/mod.rs | 2 +- 3 files changed, 430 insertions(+), 430 deletions(-) delete mode 100644 tests/sync/bootstrap.rs create mode 100644 tests/sync/historic_sync.rs (limited to 'tests/sync') diff --git a/tests/sync/bootstrap.rs b/tests/sync/bootstrap.rs deleted file mode 100644 index 8f0c79b..0000000 --- a/tests/sync/bootstrap.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Bootstrap Sync Tests -//! -//! Tests for relay synchronization from a pre-configured bootstrap relay. -//! These tests verify that a relay can sync events from another relay -//! that it's configured to connect to on startup. -//! -//! # Tests -//! - Test 1: Bootstrap sync on startup (existing events sync) -//! - Test 4: Replay after restart (events persist and replay) - -use std::time::Duration; - -use nostr_sdk::prelude::*; - -use crate::common::{sync_helpers::*, TestRelay}; - -/// Test 1: Bootstrap sync - relay syncs existing events from bootstrap relay on startup -/// -/// Scenario: -/// 1. Start relay_a (source) with an announcement -/// 2. Start relay_b configured to sync from relay_a -/// 3. Verify relay_b syncs the announcement from relay_a -/// -/// This tests that when a relay starts with a bootstrap relay configured, -/// it connects and syncs existing events. -#[tokio::test] -async fn test_bootstrap_syncs_existing_layer2_events() { - // 1. Start source relay (relay_a) - let relay_a = TestRelay::start().await; - println!( - "relay_a started at {} (domain: {})", - relay_a.url(), - relay_a.domain() - ); - - // 2. Pre-allocate port for relay_b so we can include it in the announcement - let relay_b_port = TestRelay::find_free_port(); - let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); - println!("Pre-allocated relay_b domain: {}", relay_b_domain); - - // 3. Create test keys - let keys = Keys::generate(); - - // 4. Create a repository announcement that lists BOTH relays - // This is required because relay_b's write policy checks that events reference its domain - let announcement = create_repo_announcement( - &keys, - &[&relay_a.domain(), &relay_b_domain], - "test-repo-bootstrap", - ); - let announcement_id = announcement.id; - - println!( - "Created announcement {} (kind {})", - announcement_id, - announcement.kind.as_u16() - ); - for tag in announcement.tags.iter() { - println!(" Tag: {:?}", tag.as_slice()); - } - - // 5. Send announcement to relay_a BEFORE relay_b starts - // This is key for testing bootstrap sync - let client_a = TestClient::new(relay_a.url(), keys.clone()) - .await - .expect("Failed to connect to relay_a"); - - client_a - .send_event(&announcement) - .await - .expect("Failed to send announcement to relay_a"); - println!("Announcement sent to relay_a"); - - client_a.disconnect().await; - - // 6. Wait briefly to ensure event is persisted on relay_a - tokio::time::sleep(Duration::from_millis(500)).await; - - // 7. NOW start relay_b on the pre-allocated port, configured to sync from relay_a - // The announcement already exists on relay_a, so this tests bootstrap sync - let relay_b = TestRelay::start_on_port_with_options( - relay_b_port, - Some(relay_a.url().into()), - false, - ) - .await; - println!( - "relay_b started at {} (domain: {})", - relay_b.url(), - relay_b.domain() - ); - - // 8. Wait for bootstrap sync to complete - // Bootstrap sync should happen automatically on startup - tokio::time::sleep(Duration::from_secs(3)).await; - - // 9. Verify announcement synced to relay_b - let filter = Filter::new() - .kind(Kind::Custom(KIND_REPOSITORY_STATE)) - .author(keys.public_key()); - - let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; - - // 10. Cleanup - relay_b.stop().await; - relay_a.stop().await; - - assert!( - synced, - "Announcement {} should have synced from relay_a to relay_b via bootstrap sync", - announcement_id - ); -} - -/// Test 4: Replay after restart - relay re-syncs events from bootstrap after restart -/// -/// Scenario: -/// 1. Start relay_a (bootstrap) with announcement -/// 2. Start relay_b, sync events from relay_a -/// 3. Verify sync worked -/// 4. Stop relay_b -/// 5. Restart relay_b (should re-sync from relay_a) -/// 6. Verify events are available again -/// -/// Note: Since we use in-memory database, relay_b loses events on stop. -/// This tests that the sync mechanism reconnects and re-syncs on restart. -#[tokio::test] -async fn test_relay_replays_events_after_restart() { - // 1. Start source relay (relay_a) - let relay_a = TestRelay::start().await; - println!( - "relay_a started at {} (domain: {})", - relay_a.url(), - relay_a.domain() - ); - - // 2. Start relay_b first to get its domain - let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; - println!( - "relay_b (first instance) started at {} (domain: {})", - relay_b.url(), - relay_b.domain() - ); - - // 3. Create test keys - let keys = Keys::generate(); - - // 4. Create announcement listing BOTH domains (so both relays will accept it) - let announcement = create_repo_announcement( - &keys, - &[&relay_a.domain(), &relay_b.domain()], - "test-repo-replay", - ); - let announcement_id = announcement.id; - - println!( - "Created announcement {} (kind {})", - announcement_id, - announcement.kind.as_u16() - ); - - // 5. Send announcement to relay_a - let client_a = TestClient::new(relay_a.url(), keys.clone()) - .await - .expect("Failed to connect to relay_a"); - - client_a - .send_event(&announcement) - .await - .expect("Failed to send announcement to relay_a"); - println!("Announcement sent to relay_a"); - client_a.disconnect().await; - - // 6. Wait for sync - tokio::time::sleep(Duration::from_secs(2)).await; - - // 7. Verify announcement synced to relay_b (first time) - let filter = Filter::new() - .kind(Kind::Custom(KIND_REPOSITORY_STATE)) - .author(keys.public_key()); - - let synced_first = - wait_for_event_on_relay(relay_b.url(), filter.clone(), Duration::from_secs(5)).await; - println!("First sync check: {}", synced_first); - - // 8. Stop relay_b - relay_b.stop().await; - println!("relay_b stopped"); - - // 9. Wait a moment - tokio::time::sleep(Duration::from_millis(500)).await; - - // 10. Restart relay_b (new instance with same bootstrap config) - // Note: The new relay_b will have a different domain, so we need to check - // if it can still sync the event from relay_a (which already has it) - let relay_b_new = TestRelay::start_with_sync(Some(relay_a.url().into())).await; - println!( - "relay_b (second instance) started at {} (domain: {})", - relay_b_new.url(), - relay_b_new.domain() - ); - - // 11. Wait for re-sync - tokio::time::sleep(Duration::from_secs(2)).await; - - // 12. Verify announcement is available on new relay_b - // The announcement listed the OLD relay_b domain, but since relay_a still - // has the event, new relay_b should be able to sync it via bootstrap - let synced_after_restart = - wait_for_event_on_relay(relay_b_new.url(), filter, Duration::from_secs(5)).await; - - // 13. Cleanup - relay_b_new.stop().await; - relay_a.stop().await; - - assert!( - synced_first, - "Announcement {} should have synced on first connection", - announcement_id - ); - // Note: synced_after_restart may be false because the new relay_b has a different - // domain, and the announcement only lists the old relay_b domain. This is expected - // and tests realistic behavior - relay_b_new won't accept an event that doesn't - // list its domain. The important test is that sync MECHANISM works (synced_first). - println!( - "After restart sync result: {} (may be false due to domain change)", - synced_after_restart - ); -} - -/// Test 4: Rejection - announcement not listing relay should NOT sync -/// -/// Scenario: -/// 1. relay_a (source), relay_b (sync from relay_a) -/// 2. Create announcement listing ONLY relay_a domain -/// 3. Send to relay_a -/// 4. Verify NOT synced to relay_b (write policy rejects) -/// -/// This tests that the relay's write policy correctly rejects events -/// that don't list its domain in the clone tag. -#[tokio::test] -async fn test_announcement_not_listing_relay_is_not_synced() { - // 1. Start source relay (relay_a) - let relay_a = TestRelay::start().await; - println!( - "relay_a started at {} (domain: {})", - relay_a.url(), - relay_a.domain() - ); - - // 2. Start syncing relay (relay_b) configured to sync from relay_a - let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; - println!( - "relay_b started at {} (domain: {})", - relay_b.url(), - relay_b.domain() - ); - - // 3. Create test keys - let keys = Keys::generate(); - - // 4. Wait for relay_b's sync connection to establish - // Use the sync connection helper for more reliable connection verification - match wait_for_sync_connection(relay_b.url(), 1, Duration::from_secs(5)).await { - Ok(()) => println!("Sync connection established (verified via metrics)"), - Err(e) => println!("Sync connection check: {} (continuing with test)", e), - } - - // 5. Create a repository announcement that lists ONLY relay_a - // This should NOT sync to relay_b because relay_b's write policy - // will reject events that don't list its domain - let announcement = create_repo_announcement( - &keys, - &[&relay_a.domain()], // Only relay_a, NOT relay_b - "test-repo-rejection", - ); - let announcement_id = announcement.id; - - println!( - "Created announcement {} (kind {}) - lists ONLY relay_a", - announcement_id, - announcement.kind.as_u16() - ); - for tag in announcement.tags.iter() { - println!(" Tag: {:?}", tag.as_slice()); - } - - // 6. Send announcement to relay_a - let client_a = TestClient::new(relay_a.url(), keys.clone()) - .await - .expect("Failed to connect to relay_a"); - - client_a - .send_event(&announcement) - .await - .expect("Failed to send announcement to relay_a"); - println!("Announcement sent to relay_a"); - - client_a.disconnect().await; - - // 7. Wait for potential sync attempt - // Give enough time for sync to complete if it were to happen - tokio::time::sleep(Duration::from_secs(3)).await; - - // 8. Verify announcement did NOT sync to relay_b - let filter = Filter::new() - .kind(Kind::Custom(KIND_REPOSITORY_STATE)) - .author(keys.public_key()); - - let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(2)).await; - - // 9. Cleanup - relay_b.stop().await; - relay_a.stop().await; - - assert!( - !synced, - "Announcement {} should NOT have synced to relay_b because it doesn't list relay_b's domain", - announcement_id - ); - println!("SUCCESS: Announcement was correctly rejected by relay_b (not synced)"); -} - -/// Test: History sync (bootstrap) works without NIP-77 negentropy -/// -/// This tests that HISTORY sync works when negentropy is disabled. -/// History sync means: events that existed on the source relay BEFORE -/// the syncing relay connected. -/// -/// Scenario: -/// 1. Start relay_b temporarily to get its domain (then stop it) -/// 2. Start relay_a (source) -/// 3. Create announcement listing both relay domains -/// 4. Send announcement to relay_a (event exists BEFORE relay_b connects) -/// 5. Start relay_b AGAIN on same port, with negentropy DISABLED -/// 6. relay_b should sync the pre-existing event via REQ+EOSE (history sync) -/// 7. Verify relay_b has the event -/// -/// This is different from "live sync" where events arrive after connection. -#[tokio::test] -async fn test_history_sync_without_negentropy() { - // 1. First, start relay_b temporarily just to reserve a port and get its domain - let relay_b_port = TestRelay::find_free_port(); - let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); - println!( - "Reserved port {} for relay_b (domain: {})", - relay_b_port, relay_b_domain - ); - - // 2. Start relay_a (source relay) - let relay_a = TestRelay::start().await; - println!( - "relay_a started at {} (domain: {})", - relay_a.url(), - relay_a.domain() - ); - - // 3. Create test keys - let keys = Keys::generate(); - - // 4. Create announcement listing BOTH relay domains - // This event will exist on relay_a BEFORE relay_b ever connects - let announcement = create_repo_announcement( - &keys, - &[&relay_a.domain(), &relay_b_domain], - "test-repo-history-no-negentropy", - ); - let announcement_id = announcement.id; - - println!( - "Created announcement {} (kind {})", - announcement_id, - announcement.kind.as_u16() - ); - for tag in announcement.tags.iter() { - println!(" Tag: {:?}", tag.as_slice()); - } - - // 5. Send announcement to relay_a (event now exists BEFORE relay_b connects) - let client_a = TestClient::new(relay_a.url(), keys.clone()) - .await - .expect("Failed to connect to relay_a"); - - client_a - .send_event(&announcement) - .await - .expect("Failed to send announcement to relay_a"); - println!("Announcement sent to relay_a (event exists BEFORE relay_b connects)"); - - client_a.disconnect().await; - - // 6. Wait a moment to ensure the event is stored - tokio::time::sleep(Duration::from_millis(500)).await; - - // 7. NOW start relay_b on the reserved port, with negentropy DISABLED - // This relay_b has never connected before - it needs to do HISTORY sync - let relay_b = TestRelay::start_on_port_with_options( - relay_b_port, - Some(relay_a.url().into()), - true, // disable_negentropy = true - ) - .await; - println!( - "relay_b started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", - relay_b.url(), - relay_b.domain() - ); - - // 8. Wait for history sync to complete (using REQ+EOSE, not negentropy) - tokio::time::sleep(Duration::from_secs(3)).await; - - // 9. Verify announcement synced to relay_b via HISTORY sync - let filter = Filter::new() - .kind(Kind::Custom(KIND_REPOSITORY_STATE)) - .author(keys.public_key()); - - let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; - - // 10. Cleanup - relay_b.stop().await; - relay_a.stop().await; - - assert!( - synced, - "Announcement {} should have synced from relay_a to relay_b via HISTORY sync (REQ+EOSE, negentropy disabled)", - announcement_id - ); - println!("SUCCESS: History sync works without negentropy (using REQ+EOSE fallback)"); -} diff --git a/tests/sync/historic_sync.rs b/tests/sync/historic_sync.rs new file mode 100644 index 0000000..8f0c79b --- /dev/null +++ b/tests/sync/historic_sync.rs @@ -0,0 +1,429 @@ +//! Bootstrap Sync Tests +//! +//! Tests for relay synchronization from a pre-configured bootstrap relay. +//! These tests verify that a relay can sync events from another relay +//! that it's configured to connect to on startup. +//! +//! # Tests +//! - Test 1: Bootstrap sync on startup (existing events sync) +//! - Test 4: Replay after restart (events persist and replay) + +use std::time::Duration; + +use nostr_sdk::prelude::*; + +use crate::common::{sync_helpers::*, TestRelay}; + +/// Test 1: Bootstrap sync - relay syncs existing events from bootstrap relay on startup +/// +/// Scenario: +/// 1. Start relay_a (source) with an announcement +/// 2. Start relay_b configured to sync from relay_a +/// 3. Verify relay_b syncs the announcement from relay_a +/// +/// This tests that when a relay starts with a bootstrap relay configured, +/// it connects and syncs existing events. +#[tokio::test] +async fn test_bootstrap_syncs_existing_layer2_events() { + // 1. Start source relay (relay_a) + let relay_a = TestRelay::start().await; + println!( + "relay_a started at {} (domain: {})", + relay_a.url(), + relay_a.domain() + ); + + // 2. Pre-allocate port for relay_b so we can include it in the announcement + let relay_b_port = TestRelay::find_free_port(); + let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); + println!("Pre-allocated relay_b domain: {}", relay_b_domain); + + // 3. Create test keys + let keys = Keys::generate(); + + // 4. Create a repository announcement that lists BOTH relays + // This is required because relay_b's write policy checks that events reference its domain + let announcement = create_repo_announcement( + &keys, + &[&relay_a.domain(), &relay_b_domain], + "test-repo-bootstrap", + ); + let announcement_id = announcement.id; + + println!( + "Created announcement {} (kind {})", + announcement_id, + announcement.kind.as_u16() + ); + for tag in announcement.tags.iter() { + println!(" Tag: {:?}", tag.as_slice()); + } + + // 5. Send announcement to relay_a BEFORE relay_b starts + // This is key for testing bootstrap sync + let client_a = TestClient::new(relay_a.url(), keys.clone()) + .await + .expect("Failed to connect to relay_a"); + + client_a + .send_event(&announcement) + .await + .expect("Failed to send announcement to relay_a"); + println!("Announcement sent to relay_a"); + + client_a.disconnect().await; + + // 6. Wait briefly to ensure event is persisted on relay_a + tokio::time::sleep(Duration::from_millis(500)).await; + + // 7. NOW start relay_b on the pre-allocated port, configured to sync from relay_a + // The announcement already exists on relay_a, so this tests bootstrap sync + let relay_b = TestRelay::start_on_port_with_options( + relay_b_port, + Some(relay_a.url().into()), + false, + ) + .await; + println!( + "relay_b started at {} (domain: {})", + relay_b.url(), + relay_b.domain() + ); + + // 8. Wait for bootstrap sync to complete + // Bootstrap sync should happen automatically on startup + tokio::time::sleep(Duration::from_secs(3)).await; + + // 9. Verify announcement synced to relay_b + let filter = Filter::new() + .kind(Kind::Custom(KIND_REPOSITORY_STATE)) + .author(keys.public_key()); + + let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; + + // 10. Cleanup + relay_b.stop().await; + relay_a.stop().await; + + assert!( + synced, + "Announcement {} should have synced from relay_a to relay_b via bootstrap sync", + announcement_id + ); +} + +/// Test 4: Replay after restart - relay re-syncs events from bootstrap after restart +/// +/// Scenario: +/// 1. Start relay_a (bootstrap) with announcement +/// 2. Start relay_b, sync events from relay_a +/// 3. Verify sync worked +/// 4. Stop relay_b +/// 5. Restart relay_b (should re-sync from relay_a) +/// 6. Verify events are available again +/// +/// Note: Since we use in-memory database, relay_b loses events on stop. +/// This tests that the sync mechanism reconnects and re-syncs on restart. +#[tokio::test] +async fn test_relay_replays_events_after_restart() { + // 1. Start source relay (relay_a) + let relay_a = TestRelay::start().await; + println!( + "relay_a started at {} (domain: {})", + relay_a.url(), + relay_a.domain() + ); + + // 2. Start relay_b first to get its domain + let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; + println!( + "relay_b (first instance) started at {} (domain: {})", + relay_b.url(), + relay_b.domain() + ); + + // 3. Create test keys + let keys = Keys::generate(); + + // 4. Create announcement listing BOTH domains (so both relays will accept it) + let announcement = create_repo_announcement( + &keys, + &[&relay_a.domain(), &relay_b.domain()], + "test-repo-replay", + ); + let announcement_id = announcement.id; + + println!( + "Created announcement {} (kind {})", + announcement_id, + announcement.kind.as_u16() + ); + + // 5. Send announcement to relay_a + let client_a = TestClient::new(relay_a.url(), keys.clone()) + .await + .expect("Failed to connect to relay_a"); + + client_a + .send_event(&announcement) + .await + .expect("Failed to send announcement to relay_a"); + println!("Announcement sent to relay_a"); + client_a.disconnect().await; + + // 6. Wait for sync + tokio::time::sleep(Duration::from_secs(2)).await; + + // 7. Verify announcement synced to relay_b (first time) + let filter = Filter::new() + .kind(Kind::Custom(KIND_REPOSITORY_STATE)) + .author(keys.public_key()); + + let synced_first = + wait_for_event_on_relay(relay_b.url(), filter.clone(), Duration::from_secs(5)).await; + println!("First sync check: {}", synced_first); + + // 8. Stop relay_b + relay_b.stop().await; + println!("relay_b stopped"); + + // 9. Wait a moment + tokio::time::sleep(Duration::from_millis(500)).await; + + // 10. Restart relay_b (new instance with same bootstrap config) + // Note: The new relay_b will have a different domain, so we need to check + // if it can still sync the event from relay_a (which already has it) + let relay_b_new = TestRelay::start_with_sync(Some(relay_a.url().into())).await; + println!( + "relay_b (second instance) started at {} (domain: {})", + relay_b_new.url(), + relay_b_new.domain() + ); + + // 11. Wait for re-sync + tokio::time::sleep(Duration::from_secs(2)).await; + + // 12. Verify announcement is available on new relay_b + // The announcement listed the OLD relay_b domain, but since relay_a still + // has the event, new relay_b should be able to sync it via bootstrap + let synced_after_restart = + wait_for_event_on_relay(relay_b_new.url(), filter, Duration::from_secs(5)).await; + + // 13. Cleanup + relay_b_new.stop().await; + relay_a.stop().await; + + assert!( + synced_first, + "Announcement {} should have synced on first connection", + announcement_id + ); + // Note: synced_after_restart may be false because the new relay_b has a different + // domain, and the announcement only lists the old relay_b domain. This is expected + // and tests realistic behavior - relay_b_new won't accept an event that doesn't + // list its domain. The important test is that sync MECHANISM works (synced_first). + println!( + "After restart sync result: {} (may be false due to domain change)", + synced_after_restart + ); +} + +/// Test 4: Rejection - announcement not listing relay should NOT sync +/// +/// Scenario: +/// 1. relay_a (source), relay_b (sync from relay_a) +/// 2. Create announcement listing ONLY relay_a domain +/// 3. Send to relay_a +/// 4. Verify NOT synced to relay_b (write policy rejects) +/// +/// This tests that the relay's write policy correctly rejects events +/// that don't list its domain in the clone tag. +#[tokio::test] +async fn test_announcement_not_listing_relay_is_not_synced() { + // 1. Start source relay (relay_a) + let relay_a = TestRelay::start().await; + println!( + "relay_a started at {} (domain: {})", + relay_a.url(), + relay_a.domain() + ); + + // 2. Start syncing relay (relay_b) configured to sync from relay_a + let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; + println!( + "relay_b started at {} (domain: {})", + relay_b.url(), + relay_b.domain() + ); + + // 3. Create test keys + let keys = Keys::generate(); + + // 4. Wait for relay_b's sync connection to establish + // Use the sync connection helper for more reliable connection verification + match wait_for_sync_connection(relay_b.url(), 1, Duration::from_secs(5)).await { + Ok(()) => println!("Sync connection established (verified via metrics)"), + Err(e) => println!("Sync connection check: {} (continuing with test)", e), + } + + // 5. Create a repository announcement that lists ONLY relay_a + // This should NOT sync to relay_b because relay_b's write policy + // will reject events that don't list its domain + let announcement = create_repo_announcement( + &keys, + &[&relay_a.domain()], // Only relay_a, NOT relay_b + "test-repo-rejection", + ); + let announcement_id = announcement.id; + + println!( + "Created announcement {} (kind {}) - lists ONLY relay_a", + announcement_id, + announcement.kind.as_u16() + ); + for tag in announcement.tags.iter() { + println!(" Tag: {:?}", tag.as_slice()); + } + + // 6. Send announcement to relay_a + let client_a = TestClient::new(relay_a.url(), keys.clone()) + .await + .expect("Failed to connect to relay_a"); + + client_a + .send_event(&announcement) + .await + .expect("Failed to send announcement to relay_a"); + println!("Announcement sent to relay_a"); + + client_a.disconnect().await; + + // 7. Wait for potential sync attempt + // Give enough time for sync to complete if it were to happen + tokio::time::sleep(Duration::from_secs(3)).await; + + // 8. Verify announcement did NOT sync to relay_b + let filter = Filter::new() + .kind(Kind::Custom(KIND_REPOSITORY_STATE)) + .author(keys.public_key()); + + let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(2)).await; + + // 9. Cleanup + relay_b.stop().await; + relay_a.stop().await; + + assert!( + !synced, + "Announcement {} should NOT have synced to relay_b because it doesn't list relay_b's domain", + announcement_id + ); + println!("SUCCESS: Announcement was correctly rejected by relay_b (not synced)"); +} + +/// Test: History sync (bootstrap) works without NIP-77 negentropy +/// +/// This tests that HISTORY sync works when negentropy is disabled. +/// History sync means: events that existed on the source relay BEFORE +/// the syncing relay connected. +/// +/// Scenario: +/// 1. Start relay_b temporarily to get its domain (then stop it) +/// 2. Start relay_a (source) +/// 3. Create announcement listing both relay domains +/// 4. Send announcement to relay_a (event exists BEFORE relay_b connects) +/// 5. Start relay_b AGAIN on same port, with negentropy DISABLED +/// 6. relay_b should sync the pre-existing event via REQ+EOSE (history sync) +/// 7. Verify relay_b has the event +/// +/// This is different from "live sync" where events arrive after connection. +#[tokio::test] +async fn test_history_sync_without_negentropy() { + // 1. First, start relay_b temporarily just to reserve a port and get its domain + let relay_b_port = TestRelay::find_free_port(); + let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); + println!( + "Reserved port {} for relay_b (domain: {})", + relay_b_port, relay_b_domain + ); + + // 2. Start relay_a (source relay) + let relay_a = TestRelay::start().await; + println!( + "relay_a started at {} (domain: {})", + relay_a.url(), + relay_a.domain() + ); + + // 3. Create test keys + let keys = Keys::generate(); + + // 4. Create announcement listing BOTH relay domains + // This event will exist on relay_a BEFORE relay_b ever connects + let announcement = create_repo_announcement( + &keys, + &[&relay_a.domain(), &relay_b_domain], + "test-repo-history-no-negentropy", + ); + let announcement_id = announcement.id; + + println!( + "Created announcement {} (kind {})", + announcement_id, + announcement.kind.as_u16() + ); + for tag in announcement.tags.iter() { + println!(" Tag: {:?}", tag.as_slice()); + } + + // 5. Send announcement to relay_a (event now exists BEFORE relay_b connects) + let client_a = TestClient::new(relay_a.url(), keys.clone()) + .await + .expect("Failed to connect to relay_a"); + + client_a + .send_event(&announcement) + .await + .expect("Failed to send announcement to relay_a"); + println!("Announcement sent to relay_a (event exists BEFORE relay_b connects)"); + + client_a.disconnect().await; + + // 6. Wait a moment to ensure the event is stored + tokio::time::sleep(Duration::from_millis(500)).await; + + // 7. NOW start relay_b on the reserved port, with negentropy DISABLED + // This relay_b has never connected before - it needs to do HISTORY sync + let relay_b = TestRelay::start_on_port_with_options( + relay_b_port, + Some(relay_a.url().into()), + true, // disable_negentropy = true + ) + .await; + println!( + "relay_b started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", + relay_b.url(), + relay_b.domain() + ); + + // 8. Wait for history sync to complete (using REQ+EOSE, not negentropy) + tokio::time::sleep(Duration::from_secs(3)).await; + + // 9. Verify announcement synced to relay_b via HISTORY sync + let filter = Filter::new() + .kind(Kind::Custom(KIND_REPOSITORY_STATE)) + .author(keys.public_key()); + + let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; + + // 10. Cleanup + relay_b.stop().await; + relay_a.stop().await; + + assert!( + synced, + "Announcement {} should have synced from relay_a to relay_b via HISTORY sync (REQ+EOSE, negentropy disabled)", + announcement_id + ); + println!("SUCCESS: History sync works without negentropy (using REQ+EOSE fallback)"); +} diff --git a/tests/sync/mod.rs b/tests/sync/mod.rs index cf8f599..ffda73e 100644 --- a/tests/sync/mod.rs +++ b/tests/sync/mod.rs @@ -28,7 +28,7 @@ //! - `fetch_metrics()` - Prometheus metrics fetching // Test modules -pub mod bootstrap; +pub mod historic_sync; pub mod catchup; pub mod discovery; pub mod live_sync; -- cgit v1.2.3