From 339b0c02f2cec25ae804dc882f5ce7d1dd58b9a6 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 5 Dec 2025 12:03:52 +0000 Subject: rename sunc_bootstrap_relay_url --- .env.example | 41 ++++++++++++++++++++++++++--- docs/explanation/grasp-02-proactive-sync.md | 7 +++-- docs/reference/configuration.md | 17 ++++++------ src/config.rs | 10 ++++--- src/http/nip11.rs | 6 +++-- src/main.rs | 8 +++--- src/sync/manager.rs | 33 ++++++++++++----------- tests/common/relay.rs | 14 +++++----- tests/proactive_sync_multi.rs | 2 +- 9 files changed, 90 insertions(+), 48 deletions(-) diff --git a/.env.example b/.env.example index 9207dc4..7545d03 100644 --- a/.env.example +++ b/.env.example @@ -100,12 +100,45 @@ # RUST_LOG=info # ============================================================================ -# FUTURE/PLANNED OPTIONS (not yet implemented) +# PROACTIVE SYNC (GRASP-02) # ============================================================================ -# Proactive sync settings (GRASP-02) -# NGIT_PROACTIVE_SYNC_ENABLED=true -# NGIT_PROACTIVE_SYNC_INTERVAL_SECS=3600 +# Bootstrap relay URL for initial sync (optional) +# Additional relays are automatically discovered from repository announcements +# that list our service domain. +# CLI: --sync-bootstrap-relay-url +# Default: (none - relay discovery from stored announcements only) +# NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.example.com + +# Maximum backoff time in seconds for sync relay reconnection +# CLI: --sync-max-backoff-secs +# Default: 3600 (1 hour) +# NGIT_SYNC_MAX_BACKOFF_SECS=3600 + +# Delay in seconds before running startup catchup +# CLI: --sync-startup-delay-secs +# Default: 30 +# NGIT_SYNC_STARTUP_DELAY_SECS=30 + +# Delay in seconds before running reconnect catchup +# CLI: --sync-reconnect-delay-secs +# Default: 10 +# NGIT_SYNC_RECONNECT_DELAY_SECS=10 + +# Number of days to look back for reconnect catchup +# CLI: --sync-reconnect-lookback-days +# Default: 3 +# NGIT_SYNC_RECONNECT_LOOKBACK_DAYS=3 + +# Maximum startup jitter in milliseconds for sync connections +# Set to 0 to disable jitter (useful for testing) +# CLI: --sync-startup-jitter-ms +# Default: 10000 (10 seconds) +# NGIT_SYNC_STARTUP_JITTER_MS=10000 + +# ============================================================================ +# FUTURE/PLANNED OPTIONS (not yet implemented) +# ============================================================================ # Archive mode (GRASP-05) # NGIT_ARCHIVE_MODE=false \ No newline at end of file diff --git a/docs/explanation/grasp-02-proactive-sync.md b/docs/explanation/grasp-02-proactive-sync.md index 98531ec..666b048 100644 --- a/docs/explanation/grasp-02-proactive-sync.md +++ b/docs/explanation/grasp-02-proactive-sync.md @@ -758,7 +758,8 @@ The implementation closely follows the design document with the following comple #### Phase 1: Basic Sync (commit b167f1b) - [`SyncManager`](../../src/sync/manager.rs) - Main coordinator for proactive sync -- Single relay sync via `NGIT_SYNC_RELAY_URL` configuration +- Bootstrap relay sync via `NGIT_SYNC_BOOTSTRAP_RELAY_URL` configuration +- Dynamic relay discovery from repository announcements that list our service - Event validation through existing [`Nip34WritePolicy`](../../src/nostr/builder.rs) #### Phase 2: Three-Layer Filters (commit bf558b0) @@ -844,12 +845,14 @@ All configuration via environment variables or CLI flags: | Option | Type | Default | Description | |--------|------|---------|-------------| -| `NGIT_SYNC_RELAY_URL` | String | None | Primary sync relay URL | +| `NGIT_SYNC_BOOTSTRAP_RELAY_URL` | String | None | Bootstrap relay URL for initial sync | | `NGIT_SYNC_MAX_BACKOFF_SECS` | u64 | 3600 | Max backoff delay (seconds) | | `NGIT_SYNC_STARTUP_DELAY_SECS` | u64 | 30 | Catchup delay after startup | | `NGIT_SYNC_RECONNECT_DELAY_SECS` | u64 | 10 | Catchup delay after reconnect | | `NGIT_SYNC_RECONNECT_LOOKBACK_DAYS` | u64 | 3 | Days to look back on reconnect | +**Note:** Additional relays are automatically discovered from repository announcements (kind 30617) that list our service domain. The bootstrap relay provides an initial sync source but is not required - sync will discover relays from stored announcements. + ### Module Structure (As Implemented) ``` diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 80ae45c..204fbd1 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -269,28 +269,29 @@ NGIT_DATABASE_BACKEND=lmdb These options configure the proactive sync feature that synchronizes events from other relays. -#### `NGIT_SYNC_RELAY_URL` +#### `NGIT_SYNC_BOOTSTRAP_RELAY_URL` -**Description:** URL of the primary relay to sync events from +**Description:** URL of the bootstrap relay to initially sync events from **Type:** String (WebSocket URL) -**Default:** None (sync disabled) +**Default:** None (relay discovery only) **Required:** No **Examples:** ```bash # Sync from a public relay -NGIT_SYNC_RELAY_URL=wss://relay.example.com +NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.example.com # Sync from another GRASP relay -NGIT_SYNC_RELAY_URL=wss://git.nostr.dev +NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://git.nostr.dev # Local testing -NGIT_SYNC_RELAY_URL=ws://127.0.0.1:8081 +NGIT_SYNC_BOOTSTRAP_RELAY_URL=ws://127.0.0.1:8081 ``` **Notes:** -- When set, enables proactive sync feature -- The relay will discover additional relays from repository announcements +- Bootstrap relay provides initial sync source on startup +- Additional relays are **automatically discovered** from repository announcements that list our service +- Even without a bootstrap relay, sync will discover relays from stored announcements - Synced events go through the same validation as directly-submitted events - Use WebSocket protocol (`ws://` or `wss://`) diff --git a/src/config.rs b/src/config.rs index 07e67c8..69a160a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,9 +84,10 @@ pub struct Config { #[arg(long = "metrics-top-n-repos", env = "NGIT_METRICS_TOP_N_REPOS", default_value_t = 10)] pub metrics_top_n_repos: usize, - /// URL of relay to sync kind 30617 events from (optional, enables proactive sync) - #[arg(long, env = "NGIT_SYNC_RELAY_URL")] - pub sync_relay_url: Option, + /// URL of bootstrap relay to sync from on startup (optional) + /// Sync discovers additional relays from repository announcements that list our service + #[arg(long, env = "NGIT_SYNC_BOOTSTRAP_RELAY_URL")] + pub sync_bootstrap_relay_url: Option, /// Maximum backoff time in seconds for sync relay reconnection (default: 3600 = 1 hour) #[arg(long, env = "NGIT_SYNC_MAX_BACKOFF_SECS", default_value_t = 3600)] @@ -163,11 +164,12 @@ impl Config { metrics_enabled: true, metrics_connection_per_ip_abuse_threshold: 10, metrics_top_n_repos: 10, - sync_relay_url: None, + sync_bootstrap_relay_url: None, sync_max_backoff_secs: 3600, sync_startup_delay_secs: 30, sync_reconnect_delay_secs: 10, sync_reconnect_lookback_days: 3, + sync_startup_jitter_ms: 10_000, } } } diff --git a/src/http/nip11.rs b/src/http/nip11.rs index 5d362bb..80165ee 100644 --- a/src/http/nip11.rs +++ b/src/http/nip11.rs @@ -105,11 +105,12 @@ mod tests { metrics_enabled: true, metrics_connection_per_ip_abuse_threshold: 10, metrics_top_n_repos: 10, - sync_relay_url: None, + sync_bootstrap_relay_url: None, sync_max_backoff_secs: 3600, sync_startup_delay_secs: 30, sync_reconnect_delay_secs: 10, sync_reconnect_lookback_days: 3, + sync_startup_jitter_ms: 10_000, }; let doc = RelayInformationDocument::from_config(&config); @@ -144,11 +145,12 @@ mod tests { metrics_enabled: true, metrics_connection_per_ip_abuse_threshold: 10, metrics_top_n_repos: 10, - sync_relay_url: None, + sync_bootstrap_relay_url: None, sync_max_backoff_secs: 3600, sync_startup_delay_secs: 30, sync_reconnect_delay_secs: 10, sync_reconnect_lookback_days: 3, + sync_startup_jitter_ms: 10_000, }; let doc = RelayInformationDocument::from_config(&config); diff --git a/src/main.rs b/src/main.rs index 9273afd..f887e42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,17 +52,17 @@ async fn main() -> Result<()> { ); // Start SyncManager for proactive sync (Phase 2: multi-relay support, Phase 3: health tracking) - // Even without initial sync_relay_url, SyncManager can discover relays from stored announcements + // Even without bootstrap relay, SyncManager discovers relays from stored announcements let sync_manager = SyncManager::new( - config.sync_relay_url.clone(), + config.sync_bootstrap_relay_url.clone(), config.domain.clone(), relay_with_db.database.clone(), relay_with_db.write_policy.clone(), &config, ); - if config.sync_relay_url.is_some() { - info!("Starting proactive sync from: {:?}", config.sync_relay_url); + if config.sync_bootstrap_relay_url.is_some() { + info!("Starting proactive sync with bootstrap relay: {:?}", config.sync_bootstrap_relay_url); } else { info!("Proactive sync enabled (will discover relays from stored announcements)"); } diff --git a/src/sync/manager.rs b/src/sync/manager.rs index 6fcfcd7..96bf0f4 100644 --- a/src/sync/manager.rs +++ b/src/sync/manager.rs @@ -60,8 +60,9 @@ fn get_sync_source_addr(bind_address: &str) -> SocketAddr { /// Coordinates proactive sync from configured and discovered relays pub struct SyncManager { - /// Initial relay URL to sync from (from config) - initial_relay_url: Option, + /// Bootstrap relay URL for initial sync (from config) + /// Additional relays are discovered from repository announcements that list our service + bootstrap_relay_url: Option, /// Our relay's domain (for filtering) relay_domain: String, /// Database for storing accepted events @@ -82,20 +83,20 @@ impl SyncManager { /// Create a new SyncManager /// /// # Arguments - /// * `initial_relay_url` - Optional initial relay URL from config + /// * `bootstrap_relay_url` - Optional bootstrap relay URL from config /// * `relay_domain` - Our relay's domain (used to exclude self from sync) /// * `database` - Shared database for storing events and querying announcements /// * `write_policy` - Write policy for validating synced events /// * `config` - Configuration for health tracking settings pub fn new( - initial_relay_url: Option, + bootstrap_relay_url: Option, relay_domain: String, database: SharedDatabase, write_policy: Nip34WritePolicy, config: &Config, ) -> Self { Self { - initial_relay_url, + bootstrap_relay_url, relay_domain, database, write_policy, @@ -109,14 +110,14 @@ impl SyncManager { /// Create a new SyncManager with metrics /// /// # Arguments - /// * `initial_relay_url` - Optional initial relay URL from config + /// * `bootstrap_relay_url` - Optional bootstrap relay URL from config /// * `relay_domain` - Our relay's domain (used to exclude self from sync) /// * `database` - Shared database for storing events and querying announcements /// * `write_policy` - Write policy for validating synced events /// * `config` - Configuration for health tracking settings /// * `metrics` - Sync metrics for Prometheus pub fn with_metrics( - initial_relay_url: Option, + bootstrap_relay_url: Option, relay_domain: String, database: SharedDatabase, write_policy: Nip34WritePolicy, @@ -124,7 +125,7 @@ impl SyncManager { metrics: SyncMetrics, ) -> Self { Self { - initial_relay_url, + bootstrap_relay_url, relay_domain, database, write_policy, @@ -137,14 +138,14 @@ impl SyncManager { /// Create a SyncManager with a single relay URL (Phase 1 compatibility) pub fn with_single_relay( - sync_relay_url: String, + bootstrap_url: String, database: SharedDatabase, write_policy: Nip34WritePolicy, ) -> Self { // Extract domain from URL for filtering - let relay_domain = extract_domain_from_url(&sync_relay_url).unwrap_or_default(); + let relay_domain = extract_domain_from_url(&bootstrap_url).unwrap_or_default(); Self { - initial_relay_url: Some(sync_relay_url), + bootstrap_relay_url: Some(bootstrap_url), relay_domain, database, write_policy, @@ -176,9 +177,9 @@ impl SyncManager { /// and processes incoming events. Runs indefinitely until cancelled. pub async fn run(self) { tracing::info!( - "Starting SyncManager (domain: {}, initial relay: {:?})", + "Starting SyncManager (domain: {}, bootstrap relay: {:?})", self.relay_domain, - self.initial_relay_url + self.bootstrap_relay_url ); // Create the filter service @@ -196,13 +197,13 @@ impl SyncManager { // Collect all relays to connect to let mut relays_to_connect: Vec = Vec::new(); - // Start with initial relay if configured - if let Some(ref url) = self.initial_relay_url { + // Start with bootstrap relay if configured + if let Some(ref url) = self.bootstrap_relay_url { if !self.is_own_relay(url) { relays_to_connect.push(url.clone()); active_relays.insert(url.clone()); } else { - tracing::info!("Skipping initial relay (is our own relay): {}", url); + tracing::info!("Skipping bootstrap relay (is our own relay): {}", url); } } diff --git a/tests/common/relay.rs b/tests/common/relay.rs index 21d6deb..dbfd8de 100644 --- a/tests/common/relay.rs +++ b/tests/common/relay.rs @@ -41,7 +41,7 @@ impl TestRelay { Self::start_with_options(port, None).await } - /// Start relay with sync from another relay + /// Start relay with sync from another relay (bootstrap relay) /// /// # Example /// @@ -57,12 +57,12 @@ impl TestRelay { /// source.stop().await; /// } /// ``` - pub async fn start_with_sync(sync_relay_url: &str) -> Self { - Self::start_with_options(Self::find_free_port(), Some(sync_relay_url.to_string())).await + pub async fn start_with_sync(bootstrap_relay_url: &str) -> Self { + Self::start_with_options(Self::find_free_port(), Some(bootstrap_relay_url.to_string())).await } /// Start relay with options - async fn start_with_options(port: u16, sync_relay_url: Option) -> Self { + async fn start_with_options(port: u16, bootstrap_relay_url: Option) -> Self { let bind_address = format!("127.0.0.1:{}", port); let url = format!("ws://127.0.0.1:{}", port); @@ -97,9 +97,9 @@ impl TestRelay { .stdout(Stdio::null()) .stderr(Stdio::null()); - // Add sync relay URL if provided - if let Some(ref sync_url) = sync_relay_url { - cmd.env("NGIT_SYNC_RELAY_URL", sync_url); + // Add bootstrap relay URL if provided + if let Some(ref bootstrap_url) = bootstrap_relay_url { + cmd.env("NGIT_SYNC_BOOTSTRAP_RELAY_URL", bootstrap_url); } let process = cmd.spawn().expect("Failed to start relay process"); diff --git a/tests/proactive_sync_multi.rs b/tests/proactive_sync_multi.rs index ee29f24..e07ddbe 100644 --- a/tests/proactive_sync_multi.rs +++ b/tests/proactive_sync_multi.rs @@ -170,7 +170,7 @@ async fn test_sync_configuration_applied() { tokio::time::sleep(Duration::from_millis(300)).await; // Both relays should be running - // The sync relay has NGIT_SYNC_RELAY_URL set (verified by relay starting) + // The sync relay has NGIT_SYNC_BOOTSTRAP_RELAY_URL set (verified by relay starting) let client_source = Client::default(); client_source -- cgit v1.2.3