diff --git a/VERSION b/VERSION index 83ac1cc0..c5f3c9c4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.14 +0.5.15 diff --git a/nostr_core/core_relay_pool.c b/nostr_core/core_relay_pool.c index f8ff8690..53edc184 100644 --- a/nostr_core/core_relay_pool.c +++ b/nostr_core/core_relay_pool.c @@ -510,16 +510,32 @@ static void attempt_reconnect(relay_connection_t* relay) { relay->reconnect_attempts++; if (ensure_relay_connection(relay) == 0) { - // Success! Reset reconnection state - relay->reconnect_attempts = 0; - relay->next_reconnect_time = 0; + // Success. Keep reconnect_attempts until the connection proves stable. + // This prevents rapid connect->disconnect flapping from immediately resetting backoff. + int reset_after_s = relay->pool->reconnect_config.reconnect_reset_stability_seconds; + if (reset_after_s <= 0) { + // Backward-compatible behavior: reset immediately when cooldown is disabled. + relay->reconnect_attempts = 0; + relay->next_reconnect_time = 0; + } else { + int delay_ms = calculate_reconnect_delay(relay); + int delay_s = (delay_ms + 999) / 1000; + if (delay_s < 1) { + delay_s = 1; + } + relay->next_reconnect_time = time(NULL) + delay_s; + } // Restore subscriptions on reconnect restore_subscriptions_on_reconnect(relay); } else { // Failed - schedule next attempt with backoff int delay_ms = calculate_reconnect_delay(relay); - relay->next_reconnect_time = time(NULL) + (delay_ms / 1000); + int delay_s = (delay_ms + 999) / 1000; + if (delay_s < 1) { + delay_s = 1; + } + relay->next_reconnect_time = time(NULL) + delay_s; } } @@ -618,6 +634,7 @@ nostr_pool_reconnect_config_t* nostr_pool_reconnect_config_default(void) { .initial_reconnect_delay_ms = 1000, .max_reconnect_delay_ms = 30000, .reconnect_backoff_multiplier = 2, + .reconnect_reset_stability_seconds = 30, .ping_interval_seconds = 30, .pong_timeout_seconds = 10 }; @@ -2033,6 +2050,21 @@ int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms) { relay->status = NOSTR_POOL_RELAY_CONNECTED; + // If the relay remains connected long enough, clear reconnect backoff. + if (relay->reconnect_attempts > 0) { + int reset_after_s = pool->reconnect_config.reconnect_reset_stability_seconds; + if (reset_after_s <= 0) { + relay->reconnect_attempts = 0; + relay->next_reconnect_time = 0; + } else if (relay->connect_time > 0) { + time_t now = time(NULL); + if (now - relay->connect_time >= reset_after_s) { + relay->reconnect_attempts = 0; + relay->next_reconnect_time = 0; + } + } + } + // Connection health monitoring (ping/pong) check_connection_health(relay); diff --git a/nostr_core/nip017.c b/nostr_core/nip017.c index 30b83c84..0915af38 100644 --- a/nostr_core/nip017.c +++ b/nostr_core/nip017.c @@ -354,6 +354,22 @@ cJSON* nostr_nip17_receive_dm(cJSON* gift_wrap, // Unseal the rumor cJSON* rumor = nostr_nip59_unseal_rumor(seal, sender_public_key, recipient_private_key); cJSON_Delete(seal); // Seal is no longer needed + if (!rumor) { + return NULL; + } + + // NIP-17 safety check: seal pubkey must match rumor pubkey to prevent impersonation + cJSON* rumor_pubkey_item = cJSON_GetObjectItem(rumor, "pubkey"); + if (!rumor_pubkey_item || !cJSON_IsString(rumor_pubkey_item)) { + cJSON_Delete(rumor); + return NULL; + } + + const char* rumor_pubkey_hex = cJSON_GetStringValue(rumor_pubkey_item); + if (!rumor_pubkey_hex || strcmp(sender_pubkey_hex, rumor_pubkey_hex) != 0) { + cJSON_Delete(rumor); + return NULL; + } return rumor; } diff --git a/nostr_core/nostr_core.h b/nostr_core/nostr_core.h index 877971b0..9c15ffb0 100644 --- a/nostr_core/nostr_core.h +++ b/nostr_core/nostr_core.h @@ -2,10 +2,10 @@ #define NOSTR_CORE_H // Version information (auto-updated by increment_and_push.sh) -#define VERSION "v0.5.14" +#define VERSION "v0.5.15" #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 14 +#define VERSION_PATCH 15 /* * NOSTR Core Library - Complete API Reference @@ -245,13 +245,14 @@ typedef struct nostr_pool_subscription nostr_pool_subscription_t; // Reconnection configuration typedef struct { - int enable_auto_reconnect; // 1 = enable, 0 = disable - int max_reconnect_attempts; // Max attempts per relay - int initial_reconnect_delay_ms; // Initial delay between attempts - int max_reconnect_delay_ms; // Max delay (cap exponential backoff) - int reconnect_backoff_multiplier; // Delay multiplier - int ping_interval_seconds; // How often to ping (0 = disable) - int pong_timeout_seconds; // How long to wait for pong before reconnecting + int enable_auto_reconnect; // 1 = enable, 0 = disable + int max_reconnect_attempts; // Max attempts per relay + int initial_reconnect_delay_ms; // Initial delay between attempts + int max_reconnect_delay_ms; // Max delay (cap exponential backoff) + int reconnect_backoff_multiplier; // Delay multiplier + int reconnect_reset_stability_seconds; // Connected time required before resetting reconnect_attempts + int ping_interval_seconds; // How often to ping (0 = disable) + int pong_timeout_seconds; // How long to wait for pong before reconnecting } nostr_pool_reconnect_config_t; // Relay pool management functions