Getting the relay pool up to speed

This commit is contained in:
Laan Tungir 2025-10-02 11:51:41 -04:00
parent 0d910ca181
commit 0f897ab1b3
14 changed files with 6515 additions and 308 deletions

View File

@ -302,20 +302,32 @@ publish_result_t* synchronous_publish_event_with_progress(const char** relay_url
### Relay Pools (Asynchronous) ### Relay Pools (Asynchronous)
```c ```c
// Create and manage relay pool // Create and manage relay pool with reconnection
nostr_relay_pool_t* nostr_relay_pool_create(void); nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config);
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url); int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool); void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
// Subscribe to events // Subscribe to events (with auto-reconnection)
nostr_pool_subscription_t* nostr_relay_pool_subscribe( nostr_pool_subscription_t* nostr_relay_pool_subscribe(
nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* filter, nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data), void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(void* user_data), void* user_data); void (*on_eose)(void* user_data), void* user_data, int close_on_eose);
// Run event loop // Run event loop (handles reconnection automatically)
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms); int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms); int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
// Reconnection configuration
typedef struct {
int enable_auto_reconnect; // Enable automatic reconnection
int max_reconnect_attempts; // Maximum retry attempts
int initial_reconnect_delay_ms; // Initial delay between attempts
int max_reconnect_delay_ms; // Maximum delay cap
int reconnect_backoff_multiplier; // Exponential backoff factor
int ping_interval_seconds; // Health check ping interval
int pong_timeout_seconds; // Pong response timeout
} nostr_pool_reconnect_config_t;
``` ```
### NIP-05 Identifier Verification ### NIP-05 Identifier Verification
@ -370,6 +382,9 @@ The library includes extensive tests:
# Individual test categories # Individual test categories
cd tests && make test cd tests && make test
# Interactive relay pool testing
cd tests && ./pool_test
``` ```
**Test Categories:** **Test Categories:**
@ -383,6 +398,7 @@ cd tests && make test
- **Relay Communication**: `relay_pool_test`, `sync_test` - **Relay Communication**: `relay_pool_test`, `sync_test`
- **HTTP/WebSocket**: `http_test`, `wss_test` - **HTTP/WebSocket**: `http_test`, `wss_test`
- **Proof of Work**: `test_pow_loop` - **Proof of Work**: `test_pow_loop`
- **Interactive Pool Testing**: `pool_test` (menu-driven interface with reconnection testing)
## 🏗️ Integration ## 🏗️ Integration

View File

@ -491,6 +491,7 @@ SOURCES="$SOURCES cjson/cJSON.c"
SOURCES="$SOURCES nostr_core/utils.c" SOURCES="$SOURCES nostr_core/utils.c"
SOURCES="$SOURCES nostr_core/nostr_common.c" SOURCES="$SOURCES nostr_core/nostr_common.c"
SOURCES="$SOURCES nostr_core/core_relays.c" SOURCES="$SOURCES nostr_core/core_relays.c"
SOURCES="$SOURCES nostr_core/core_relay_pool.c"
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c" SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
SOURCES="$SOURCES nostr_core/request_validator.c" SOURCES="$SOURCES nostr_core/request_validator.c"

4365
debug.log

File diff suppressed because one or more lines are too long

View File

@ -65,22 +65,26 @@ typedef struct relay_connection {
char* url; char* url;
nostr_ws_client_t* ws_client; nostr_ws_client_t* ws_client;
nostr_pool_relay_status_t status; nostr_pool_relay_status_t status;
// Connection management // Connection management
time_t last_ping;
time_t connect_time; time_t connect_time;
nostr_relay_pool_t* pool; // Back reference to pool for config access
// Reconnection management
int reconnect_attempts; int reconnect_attempts;
time_t last_reconnect_attempt;
// Ping management for latency measurement time_t next_reconnect_time;
// Connection health monitoring (ping/pong)
time_t last_ping_sent; time_t last_ping_sent;
time_t next_ping_time; // last_ping_sent + NOSTR_POOL_PING_INTERVAL time_t last_pong_received;
int ping_pending;
double pending_ping_start_ms; // High-resolution timestamp for ping measurement double pending_ping_start_ms; // High-resolution timestamp for ping measurement
int ping_pending; // Flag to track if ping response is expected
// Multi-subscription latency tracking (REQ->first EVENT/EOSE) // Multi-subscription latency tracking (REQ->first EVENT/EOSE)
subscription_timing_t pending_subscriptions[NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS]; subscription_timing_t pending_subscriptions[NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS];
int pending_subscription_count; int pending_subscription_count;
// Statistics // Statistics
nostr_relay_stats_t stats; nostr_relay_stats_t stats;
} relay_connection_t; } relay_connection_t;
@ -88,34 +92,38 @@ typedef struct relay_connection {
struct nostr_pool_subscription { struct nostr_pool_subscription {
char subscription_id[NOSTR_POOL_SUBSCRIPTION_ID_SIZE]; char subscription_id[NOSTR_POOL_SUBSCRIPTION_ID_SIZE];
cJSON* filter; cJSON* filter;
// Relay-specific subscription tracking // Relay-specific subscription tracking
char** relay_urls; char** relay_urls;
int relay_count; int relay_count;
int* eose_received; // Track EOSE from each relay int* eose_received; // Track EOSE from each relay
// Callbacks // Callbacks
void (*on_event)(cJSON* event, const char* relay_url, void* user_data); void (*on_event)(cJSON* event, const char* relay_url, void* user_data);
void (*on_eose)(void* user_data); void (*on_eose)(void* user_data);
void* user_data; void* user_data;
int closed; int closed;
int close_on_eose; // Auto-close subscription when all relays send EOSE
nostr_relay_pool_t* pool; // Back reference to pool nostr_relay_pool_t* pool; // Back reference to pool
}; };
struct nostr_relay_pool { struct nostr_relay_pool {
relay_connection_t* relays[NOSTR_POOL_MAX_RELAYS]; relay_connection_t* relays[NOSTR_POOL_MAX_RELAYS];
int relay_count; int relay_count;
// Reconnection configuration
nostr_pool_reconnect_config_t reconnect_config;
// Event deduplication - simple hash table with linear probing // Event deduplication - simple hash table with linear probing
char seen_event_ids[NOSTR_POOL_MAX_SEEN_EVENTS][65]; // 64 hex chars + null terminator char seen_event_ids[NOSTR_POOL_MAX_SEEN_EVENTS][65]; // 64 hex chars + null terminator
int seen_count; int seen_count;
int seen_next_index; int seen_next_index;
// Active subscriptions // Active subscriptions
nostr_pool_subscription_t* subscriptions[NOSTR_POOL_MAX_SUBSCRIPTIONS]; nostr_pool_subscription_t* subscriptions[NOSTR_POOL_MAX_SUBSCRIPTIONS];
int subscription_count; int subscription_count;
// Pool-wide settings // Pool-wide settings
int default_timeout_ms; int default_timeout_ms;
}; };
@ -163,33 +171,211 @@ static int add_subscription_timing(relay_connection_t* relay, const char* subscr
// Helper function to find and remove subscription timing // Helper function to find and remove subscription timing
static double remove_subscription_timing(relay_connection_t* relay, const char* subscription_id) { static double remove_subscription_timing(relay_connection_t* relay, const char* subscription_id) {
if (!relay || !subscription_id) return -1.0; if (!relay || !subscription_id) return -1.0;
for (int i = 0; i < relay->pending_subscription_count; i++) { for (int i = 0; i < relay->pending_subscription_count; i++) {
if (relay->pending_subscriptions[i].active && if (relay->pending_subscriptions[i].active &&
strcmp(relay->pending_subscriptions[i].subscription_id, subscription_id) == 0) { strcmp(relay->pending_subscriptions[i].subscription_id, subscription_id) == 0) {
// Calculate latency // Calculate latency
double current_time_ms = get_current_time_ms(); double current_time_ms = get_current_time_ms();
double latency_ms = current_time_ms - relay->pending_subscriptions[i].start_time_ms; double latency_ms = current_time_ms - relay->pending_subscriptions[i].start_time_ms;
// Mark as inactive and remove by shifting remaining entries // Mark as inactive and remove by shifting remaining entries
relay->pending_subscriptions[i].active = 0; relay->pending_subscriptions[i].active = 0;
for (int j = i; j < relay->pending_subscription_count - 1; j++) { for (int j = i; j < relay->pending_subscription_count - 1; j++) {
relay->pending_subscriptions[j] = relay->pending_subscriptions[j + 1]; relay->pending_subscriptions[j] = relay->pending_subscriptions[j + 1];
} }
relay->pending_subscription_count--; relay->pending_subscription_count--;
return latency_ms; return latency_ms;
} }
} }
return -1.0; // Not found return -1.0; // Not found
} }
// Helper function to check if event ID has been seen // Helper function to ensure relay connection
static int ensure_relay_connection(relay_connection_t* relay) {
if (!relay) {
return -1;
}
if (relay->ws_client && nostr_ws_get_state(relay->ws_client) == NOSTR_WS_CONNECTED) {
relay->status = NOSTR_POOL_RELAY_CONNECTED;
return 0; // Already connected
}
// Close existing connection if any
if (relay->ws_client) {
nostr_ws_close(relay->ws_client);
relay->ws_client = NULL;
}
// Attempt connection
relay->status = NOSTR_POOL_RELAY_CONNECTING;
relay->stats.connection_attempts++;
relay->ws_client = nostr_ws_connect(relay->url);
if (!relay->ws_client) {
relay->status = NOSTR_POOL_RELAY_ERROR;
relay->reconnect_attempts++;
relay->stats.connection_failures++;
return -1;
}
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
if (state == NOSTR_WS_CONNECTED) {
relay->status = NOSTR_POOL_RELAY_CONNECTED;
relay->connect_time = time(NULL);
relay->reconnect_attempts = 0;
// Initialize ping/pong monitoring on new connection
relay->last_ping_sent = time(NULL);
relay->last_pong_received = time(NULL);
relay->ping_pending = 0;
return 0;
} else {
relay->status = NOSTR_POOL_RELAY_ERROR;
relay->reconnect_attempts++;
relay->stats.connection_failures++;
// Close the failed connection
nostr_ws_close(relay->ws_client);
relay->ws_client = NULL;
return -1;
}
}
// Reconnection helper functions
static int should_attempt_reconnect(relay_connection_t* relay) {
if (!relay->pool->reconnect_config.enable_auto_reconnect) return 0;
if (relay->status == NOSTR_POOL_RELAY_CONNECTED) return 0;
if (relay->reconnect_attempts >= relay->pool->reconnect_config.max_reconnect_attempts) return 0;
time_t now = time(NULL);
return (now >= relay->next_reconnect_time);
}
static int calculate_reconnect_delay(relay_connection_t* relay) {
int delay = relay->pool->reconnect_config.initial_reconnect_delay_ms;
// Apply exponential backoff
for (int i = 1; i < relay->reconnect_attempts; i++) {
delay *= relay->pool->reconnect_config.reconnect_backoff_multiplier;
if (delay > relay->pool->reconnect_config.max_reconnect_delay_ms) {
delay = relay->pool->reconnect_config.max_reconnect_delay_ms;
break;
}
}
return delay;
}
static void restore_subscriptions_on_reconnect(relay_connection_t* relay) {
// Find subscriptions that should be active on this relay
for (int i = 0; i < relay->pool->subscription_count; i++) {
nostr_pool_subscription_t* sub = relay->pool->subscriptions[i];
if (!sub->closed) {
// Check if this subscription should be on this relay
for (int j = 0; j < sub->relay_count; j++) {
if (strcmp(sub->relay_urls[j], relay->url) == 0) {
// Re-send the subscription
if (nostr_relay_send_req(relay->ws_client, sub->subscription_id, sub->filter) >= 0) {
// Add timing for latency measurement
add_subscription_timing(relay, sub->subscription_id);
}
break;
}
}
}
}
}
static void attempt_reconnect(relay_connection_t* relay) {
relay->status = NOSTR_POOL_RELAY_CONNECTING;
relay->last_reconnect_attempt = time(NULL);
relay->reconnect_attempts++;
if (ensure_relay_connection(relay) == 0) {
// Success! Reset reconnection state
relay->reconnect_attempts = 0;
relay->next_reconnect_time = 0;
// 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);
}
}
// Connection health monitoring (ping/pong)
static void check_connection_health(relay_connection_t* relay) {
time_t now = time(NULL);
// Send ping if interval elapsed and ping is enabled
if (relay->pool->reconnect_config.ping_interval_seconds > 0 &&
now - relay->last_ping_sent >= relay->pool->reconnect_config.ping_interval_seconds &&
!relay->ping_pending) {
if (nostr_ws_ping(relay->ws_client) == 0) {
relay->last_ping_sent = now;
relay->ping_pending = 1;
// Store high-resolution start time for latency measurement
relay->pending_ping_start_ms = get_current_time_ms();
}
}
// Check for pong timeout
if (relay->ping_pending &&
now - relay->last_ping_sent > relay->pool->reconnect_config.pong_timeout_seconds) {
// No pong received - connection is dead
relay->status = NOSTR_POOL_RELAY_DISCONNECTED;
relay->ping_pending = 0;
}
}
static void handle_pong_response(relay_connection_t* relay) {
relay->last_pong_received = time(NULL);
if (relay->ping_pending) {
// Calculate ping latency
double current_time_ms = get_current_time_ms();
double ping_latency = current_time_ms - relay->pending_ping_start_ms;
// Update ping statistics
if (relay->stats.ping_samples == 0) {
relay->stats.ping_latency_avg = ping_latency;
relay->stats.ping_latency_min = ping_latency;
relay->stats.ping_latency_max = ping_latency;
} else {
relay->stats.ping_latency_avg =
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
(relay->stats.ping_samples + 1);
if (ping_latency < relay->stats.ping_latency_min) {
relay->stats.ping_latency_min = ping_latency;
}
if (ping_latency > relay->stats.ping_latency_max) {
relay->stats.ping_latency_max = ping_latency;
}
}
relay->stats.ping_latency_current = ping_latency;
relay->stats.ping_samples++;
relay->ping_pending = 0;
}
}
static int is_event_seen(nostr_relay_pool_t* pool, const char* event_id) { static int is_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
if (!pool || !event_id) return 0; if (!pool || !event_id) return 0;
for (int i = 0; i < pool->seen_count; i++) { for (int i = 0; i < pool->seen_count; i++) {
if (strcmp(pool->seen_event_ids[i], event_id) == 0) { if (strcmp(pool->seen_event_ids[i], event_id) == 0) {
return 1; return 1;
@ -201,28 +387,49 @@ static int is_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
// Helper function to mark event as seen // Helper function to mark event as seen
static void mark_event_seen(nostr_relay_pool_t* pool, const char* event_id) { static void mark_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
if (!pool || !event_id) return; if (!pool || !event_id) return;
// Don't add duplicates // Don't add duplicates
if (is_event_seen(pool, event_id)) return; if (is_event_seen(pool, event_id)) return;
// Use circular buffer for seen events // Use circular buffer for seen events
strncpy(pool->seen_event_ids[pool->seen_next_index], event_id, 64); strncpy(pool->seen_event_ids[pool->seen_next_index], event_id, 64);
pool->seen_event_ids[pool->seen_next_index][64] = '\0'; pool->seen_event_ids[pool->seen_next_index][64] = '\0';
pool->seen_next_index = (pool->seen_next_index + 1) % NOSTR_POOL_MAX_SEEN_EVENTS; pool->seen_next_index = (pool->seen_next_index + 1) % NOSTR_POOL_MAX_SEEN_EVENTS;
if (pool->seen_count < NOSTR_POOL_MAX_SEEN_EVENTS) { if (pool->seen_count < NOSTR_POOL_MAX_SEEN_EVENTS) {
pool->seen_count++; pool->seen_count++;
} }
} }
// Default configuration helper
nostr_pool_reconnect_config_t* nostr_pool_reconnect_config_default(void) {
static nostr_pool_reconnect_config_t config = {
.enable_auto_reconnect = 1,
.max_reconnect_attempts = 10,
.initial_reconnect_delay_ms = 1000,
.max_reconnect_delay_ms = 30000,
.reconnect_backoff_multiplier = 2,
.ping_interval_seconds = 30,
.pong_timeout_seconds = 10
};
return &config;
}
// Pool management functions // Pool management functions
nostr_relay_pool_t* nostr_relay_pool_create(void) { nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config) {
if (!config) {
config = nostr_pool_reconnect_config_default();
}
nostr_relay_pool_t* pool = calloc(1, sizeof(nostr_relay_pool_t)); nostr_relay_pool_t* pool = calloc(1, sizeof(nostr_relay_pool_t));
if (!pool) { if (!pool) {
return NULL; return NULL;
} }
// Copy configuration
pool->reconnect_config = *config;
pool->default_timeout_ms = NOSTR_POOL_DEFAULT_TIMEOUT; pool->default_timeout_ms = NOSTR_POOL_DEFAULT_TIMEOUT;
return pool; return pool;
} }
@ -250,15 +457,19 @@ int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url)
relay->status = NOSTR_POOL_RELAY_DISCONNECTED; relay->status = NOSTR_POOL_RELAY_DISCONNECTED;
relay->ws_client = NULL; relay->ws_client = NULL;
relay->last_ping = 0;
relay->connect_time = 0; relay->connect_time = 0;
relay->pool = pool; // Set back reference
// Initialize reconnection state
relay->reconnect_attempts = 0; relay->reconnect_attempts = 0;
relay->last_reconnect_attempt = 0;
// Initialize ping management relay->next_reconnect_time = 0;
// Initialize ping/pong monitoring
relay->last_ping_sent = 0; relay->last_ping_sent = 0;
relay->next_ping_time = 0; relay->last_pong_received = 0;
relay->pending_ping_start_ms = 0.0;
relay->ping_pending = 0; relay->ping_pending = 0;
relay->pending_ping_start_ms = 0.0;
// Initialize statistics // Initialize statistics
memset(&relay->stats, 0, sizeof(relay->stats)); memset(&relay->stats, 0, sizeof(relay->stats));
@ -325,85 +536,17 @@ void nostr_relay_pool_destroy(nostr_relay_pool_t* pool) {
free(pool); free(pool);
} }
// Helper function to ensure relay connection
static int ensure_relay_connection(relay_connection_t* relay) {
if (!relay) {
return -1;
}
if (relay->ws_client && nostr_ws_get_state(relay->ws_client) == NOSTR_WS_CONNECTED) {
relay->status = NOSTR_POOL_RELAY_CONNECTED;
return 0; // Already connected
}
// Close existing connection if any
if (relay->ws_client) {
nostr_ws_close(relay->ws_client);
relay->ws_client = NULL;
}
// Attempt connection
relay->status = NOSTR_POOL_RELAY_CONNECTING;
relay->stats.connection_attempts++;
relay->ws_client = nostr_ws_connect(relay->url);
if (!relay->ws_client) {
relay->status = NOSTR_POOL_RELAY_ERROR;
relay->reconnect_attempts++;
relay->stats.connection_failures++;
return -1;
}
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
if (state == NOSTR_WS_CONNECTED) {
relay->status = NOSTR_POOL_RELAY_CONNECTED;
relay->connect_time = time(NULL);
relay->reconnect_attempts = 0;
// PING FUNCTIONALITY DISABLED - Initial ping on connection establishment
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
// Trigger immediate ping on new connection
time_t current_time = time(NULL);
relay->pending_ping_start_ms = get_current_time_ms();
relay->ping_pending = 1;
relay->last_ping_sent = current_time;
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
relay->ping_pending = 0;
}
*/
return 0;
} else {
relay->status = NOSTR_POOL_RELAY_ERROR;
relay->reconnect_attempts++;
relay->stats.connection_failures++;
// Close the failed connection
nostr_ws_close(relay->ws_client);
relay->ws_client = NULL;
return -1;
}
}
// Subscription management // Subscription management
nostr_pool_subscription_t* nostr_relay_pool_subscribe( nostr_pool_subscription_t* nostr_relay_pool_subscribe(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char** relay_urls, const char** relay_urls,
int relay_count, int relay_count,
cJSON* filter, cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data), void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(void* user_data), void (*on_eose)(void* user_data),
void* user_data) { void* user_data,
int close_on_eose) {
if (!pool || !relay_urls || relay_count <= 0 || !filter || if (!pool || !relay_urls || relay_count <= 0 || !filter ||
pool->subscription_count >= NOSTR_POOL_MAX_SUBSCRIPTIONS) { pool->subscription_count >= NOSTR_POOL_MAX_SUBSCRIPTIONS) {
@ -458,6 +601,7 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
sub->on_eose = on_eose; sub->on_eose = on_eose;
sub->user_data = user_data; sub->user_data = user_data;
sub->closed = 0; sub->closed = 0;
sub->close_on_eose = close_on_eose;
sub->pool = pool; sub->pool = pool;
// Add to pool // Add to pool
@ -630,8 +774,15 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
} }
} }
if (all_eose && sub->on_eose) { if (all_eose) {
sub->on_eose(sub->user_data); if (sub->on_eose) {
sub->on_eose(sub->user_data);
}
// Auto-close subscription if close_on_eose is enabled
if (sub->close_on_eose && !sub->closed) {
nostr_pool_subscription_close(sub);
}
} }
break; break;
} }
@ -652,39 +803,8 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
} }
} }
} else if (strcmp(msg_type, "PONG") == 0) { } else if (strcmp(msg_type, "PONG") == 0) {
// PING FUNCTIONALITY DISABLED - Handle PONG response // Handle PONG response for connection health monitoring
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED handle_pong_response(relay);
if (relay->ping_pending) {
double current_time_ms = get_current_time_ms();
double ping_latency = current_time_ms - relay->pending_ping_start_ms;
// Update ping statistics
if (relay->stats.ping_samples == 0) {
relay->stats.ping_latency_avg = ping_latency;
relay->stats.ping_latency_min = ping_latency;
relay->stats.ping_latency_max = ping_latency;
} else {
relay->stats.ping_latency_avg =
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
(relay->stats.ping_samples + 1);
if (ping_latency < relay->stats.ping_latency_min) {
relay->stats.ping_latency_min = ping_latency;
}
if (ping_latency > relay->stats.ping_latency_max) {
relay->stats.ping_latency_max = ping_latency;
}
}
relay->stats.ping_latency_current = ping_latency;
relay->stats.ping_samples++;
relay->ping_pending = 0;
#ifdef NOSTR_DEBUG_ENABLED
printf("🏓 DEBUG: PONG from %s - latency: %.2f ms\n", relay->url, ping_latency);
#endif
}
*/
} }
if (msg_type) free(msg_type); if (msg_type) free(msg_type);
@ -757,11 +877,17 @@ cJSON** nostr_relay_pool_query_sync(
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 100); int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 100);
if (len > 0) { if (len > 0) {
buffer[len] = '\0'; buffer[len] = '\0';
char* msg_type = NULL; // Check if this is a pong message (WebSocket library prefixes pong messages)
cJSON* parsed = NULL; if (strncmp(buffer, "__PONG__", 8) == 0) {
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) { // Handle pong response for connection health monitoring
if (msg_type && strcmp(msg_type, "EVENT") == 0) { handle_pong_response(relay);
} else {
// Process as regular NOSTR message
char* msg_type = NULL;
cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
if (msg_type && strcmp(msg_type, "EVENT") == 0) {
// Handle EVENT message // Handle EVENT message
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) { if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1); cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
@ -806,6 +932,7 @@ cJSON** nostr_relay_pool_query_sync(
} }
if (msg_type) free(msg_type); if (msg_type) free(msg_type);
if (parsed) cJSON_Delete(parsed); if (parsed) cJSON_Delete(parsed);
}
} }
} }
} }
@ -1059,76 +1186,78 @@ double nostr_relay_pool_get_relay_query_latency(
} }
int nostr_relay_pool_ping_relay( int nostr_relay_pool_ping_relay(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url) { const char* relay_url) {
// PING FUNCTIONALITY DISABLED // PING FUNCTIONALITY DISABLED
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED /* COMMENTED OUT - PING FUNCTIONALITY DISABLED
if (!pool || !relay_url) { if (!pool || !relay_url) {
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
} }
relay_connection_t* relay = find_relay_by_url(pool, relay_url); relay_connection_t* relay = find_relay_by_url(pool, relay_url);
if (!relay || !relay->ws_client) { if (!relay || !relay->ws_client) {
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
} }
if (ensure_relay_connection(relay) != 0) { if (ensure_relay_connection(relay) != 0) {
return NOSTR_ERROR_NETWORK_FAILED; return NOSTR_ERROR_NETWORK_FAILED;
} }
time_t current_time = time(NULL); time_t current_time = time(NULL);
relay->pending_ping_start_ms = get_current_time_ms(); relay->pending_ping_start_ms = get_current_time_ms();
relay->ping_pending = 1; relay->ping_pending = 1;
relay->last_ping_sent = current_time; relay->last_ping_sent = current_time;
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL; relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) { if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
relay->ping_pending = 0; relay->ping_pending = 0;
return NOSTR_ERROR_NETWORK_FAILED; return NOSTR_ERROR_NETWORK_FAILED;
} }
return NOSTR_SUCCESS; return NOSTR_SUCCESS;
*/ */
(void)pool; // Suppress unused parameter warning
(void)relay_url; // Suppress unused parameter warning
return NOSTR_ERROR_INVALID_INPUT; // Function disabled return NOSTR_ERROR_INVALID_INPUT; // Function disabled
} }
int nostr_relay_pool_ping_relay_sync( int nostr_relay_pool_ping_relay_sync(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url, const char* relay_url,
int timeout_seconds) { int timeout_seconds) {
// PING FUNCTIONALITY DISABLED // PING FUNCTIONALITY DISABLED
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED /* COMMENTED OUT - PING FUNCTIONALITY DISABLED
if (!pool || !relay_url) { if (!pool || !relay_url) {
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
} }
relay_connection_t* relay = find_relay_by_url(pool, relay_url); relay_connection_t* relay = find_relay_by_url(pool, relay_url);
if (!relay || !relay->ws_client) { if (!relay || !relay->ws_client) {
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
} }
if (ensure_relay_connection(relay) != 0) { if (ensure_relay_connection(relay) != 0) {
return NOSTR_ERROR_NETWORK_FAILED; return NOSTR_ERROR_NETWORK_FAILED;
} }
if (timeout_seconds <= 0) { if (timeout_seconds <= 0) {
timeout_seconds = 5; timeout_seconds = 5;
} }
time_t current_time = time(NULL); time_t current_time = time(NULL);
relay->pending_ping_start_ms = get_current_time_ms(); relay->pending_ping_start_ms = get_current_time_ms();
relay->ping_pending = 1; relay->ping_pending = 1;
relay->last_ping_sent = current_time; relay->last_ping_sent = current_time;
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL; relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) { if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
relay->ping_pending = 0; relay->ping_pending = 0;
return NOSTR_ERROR_NETWORK_FAILED; return NOSTR_ERROR_NETWORK_FAILED;
} }
// Wait for PONG response // Wait for PONG response
time_t wait_start = time(NULL); time_t wait_start = time(NULL);
while (time(NULL) - wait_start < timeout_seconds && relay->ping_pending) { while (time(NULL) - wait_start < timeout_seconds && relay->ping_pending) {
@ -1136,7 +1265,7 @@ int nostr_relay_pool_ping_relay_sync(
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 1000); int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 1000);
if (len > 0) { if (len > 0) {
buffer[len] = '\0'; buffer[len] = '\0';
char* msg_type = NULL; char* msg_type = NULL;
cJSON* parsed = NULL; cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) { if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
@ -1146,17 +1275,17 @@ int nostr_relay_pool_ping_relay_sync(
if (relay->ping_pending) { if (relay->ping_pending) {
double current_time_ms = get_current_time_ms(); double current_time_ms = get_current_time_ms();
double ping_latency = current_time_ms - relay->pending_ping_start_ms; double ping_latency = current_time_ms - relay->pending_ping_start_ms;
// Update ping statistics // Update ping statistics
if (relay->stats.ping_samples == 0) { if (relay->stats.ping_samples == 0) {
relay->stats.ping_latency_avg = ping_latency; relay->stats.ping_latency_avg = ping_latency;
relay->stats.ping_latency_min = ping_latency; relay->stats.ping_latency_min = ping_latency;
relay->stats.ping_latency_max = ping_latency; relay->stats.ping_latency_max = ping_latency;
} else { } else {
relay->stats.ping_latency_avg = relay->stats.ping_latency_avg =
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) / (relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
(relay->stats.ping_samples + 1); (relay->stats.ping_samples + 1);
if (ping_latency < relay->stats.ping_latency_min) { if (ping_latency < relay->stats.ping_latency_min) {
relay->stats.ping_latency_min = ping_latency; relay->stats.ping_latency_min = ping_latency;
} }
@ -1164,11 +1293,11 @@ int nostr_relay_pool_ping_relay_sync(
relay->stats.ping_latency_max = ping_latency; relay->stats.ping_latency_max = ping_latency;
} }
} }
relay->stats.ping_latency_current = ping_latency; relay->stats.ping_latency_current = ping_latency;
relay->stats.ping_samples++; relay->stats.ping_samples++;
relay->ping_pending = 0; relay->ping_pending = 0;
if (msg_type) free(msg_type); if (msg_type) free(msg_type);
if (parsed) cJSON_Delete(parsed); if (parsed) cJSON_Delete(parsed);
return NOSTR_SUCCESS; return NOSTR_SUCCESS;
@ -1179,12 +1308,15 @@ int nostr_relay_pool_ping_relay_sync(
} }
} }
} }
// Timeout // Timeout
relay->ping_pending = 0; relay->ping_pending = 0;
return NOSTR_ERROR_NETWORK_FAILED; return NOSTR_ERROR_NETWORK_FAILED;
*/ */
(void)pool; // Suppress unused parameter warning
(void)relay_url; // Suppress unused parameter warning
(void)timeout_seconds; // Suppress unused parameter warning
return NOSTR_ERROR_INVALID_INPUT; // Function disabled return NOSTR_ERROR_INVALID_INPUT; // Function disabled
} }
@ -1233,52 +1365,58 @@ int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms) {
if (!pool) { if (!pool) {
return -1; return -1;
} }
int events_processed = 0; int events_processed = 0;
for (int i = 0; i < pool->relay_count; i++) { for (int i = 0; i < pool->relay_count; i++) {
relay_connection_t* relay = pool->relays[i]; relay_connection_t* relay = pool->relays[i];
if (!relay || !relay->ws_client) { if (!relay) {
continue; continue;
} }
// Check if reconnection is needed
if (should_attempt_reconnect(relay)) {
attempt_reconnect(relay);
}
// Skip if no WebSocket client
if (!relay->ws_client) {
continue;
}
// Check connection state // Check connection state
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client); nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
if (state != NOSTR_WS_CONNECTED) { if (state != NOSTR_WS_CONNECTED) {
relay->status = (state == NOSTR_WS_ERROR) ? NOSTR_POOL_RELAY_ERROR : NOSTR_POOL_RELAY_DISCONNECTED; relay->status = (state == NOSTR_WS_ERROR) ? NOSTR_POOL_RELAY_ERROR : NOSTR_POOL_RELAY_DISCONNECTED;
continue; continue;
} }
relay->status = NOSTR_POOL_RELAY_CONNECTED; relay->status = NOSTR_POOL_RELAY_CONNECTED;
// PING FUNCTIONALITY DISABLED - Automatic ping management // Connection health monitoring (ping/pong)
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED check_connection_health(relay);
// Check if we need to send a ping to keep the connection alive
if (current_time >= relay->next_ping_time && !relay->ping_pending) {
relay->pending_ping_start_ms = get_current_time_ms();
relay->ping_pending = 1;
relay->last_ping_sent = current_time;
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
relay->ping_pending = 0;
}
}
*/
// Process incoming messages // Process incoming messages
char buffer[8192]; char buffer[8192];
int timeout_per_relay = timeout_ms / pool->relay_count; int timeout_per_relay = timeout_ms / pool->relay_count;
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, timeout_per_relay); int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, timeout_per_relay);
if (len > 0) { if (len > 0) {
buffer[len] = '\0'; buffer[len] = '\0';
process_relay_message(pool, relay, buffer);
// Check if this is a pong message (WebSocket library prefixes pong messages)
if (strncmp(buffer, "__PONG__", 8) == 0) {
// Handle pong response for connection health monitoring
handle_pong_response(relay);
} else {
// Process as regular NOSTR message
process_relay_message(pool, relay, buffer);
}
events_processed++; events_processed++;
} }
} }
return events_processed; return events_processed;
} }

View File

@ -184,8 +184,20 @@ typedef struct {
typedef struct nostr_relay_pool nostr_relay_pool_t; typedef struct nostr_relay_pool nostr_relay_pool_t;
typedef struct nostr_pool_subscription nostr_pool_subscription_t; 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
} nostr_pool_reconnect_config_t;
// Relay pool management functions // Relay pool management functions
nostr_relay_pool_t* nostr_relay_pool_create(void); nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config);
nostr_pool_reconnect_config_t* nostr_pool_reconnect_config_default(void);
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url); int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url); int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool); void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
@ -198,7 +210,8 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
cJSON* filter, cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data), void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(void* user_data), void (*on_eose)(void* user_data),
void* user_data); void* user_data,
int close_on_eose);
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription); int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
// Event loop functions // Event loop functions
@ -242,6 +255,9 @@ int nostr_relay_pool_reset_relay_stats(
double nostr_relay_pool_get_relay_query_latency( double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url); const char* relay_url);
double nostr_relay_pool_get_relay_ping_latency(
nostr_relay_pool_t* pool,
const char* relay_url);
// Synchronous relay operations (one-off queries/publishes) // Synchronous relay operations (one-off queries/publishes)
typedef enum { typedef enum {

852
pool.log Normal file
View File

@ -0,0 +1,852 @@
[Thu Oct 2 11:42:57 2025] 🚀 Pool test started
[Thu Oct 2 11:43:01 2025] ðŸ<C3B0>Š Pool started with default relay
[Thu Oct 2 11:43:14 2025] âž• Relay added: wss://nos.lol
[Thu Oct 2 11:43:36 2025] ðŸ”<C5B8> New subscription created (ID: 1)
Filter: {
"kinds": [1],
"since": 1759419809,
"limit": 10
}
[Thu Oct 2 11:43:36 2025] 📨 EVENT from wss://nos.lol
├── ID: 5449492f915a...
├── Pubkey: c231760b10ce...
├── Kind: 1
├── Created: 1759419811
└── Content: 💜💜💜💜💜💜💜💜💜💜💜💜 https://nostr.download/e27f32b258bbdfe1e73dc7d06...
[Thu Oct 2 11:43:36 2025] 📋 EOSE received - all stored events delivered
[Thu Oct 2 11:43:41 2025] 📨 EVENT from wss://nos.lol
├── ID: caf09bb60e64...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759419821
└── Content: john is the best
he likes to just stand there
he likes to eat sugar cubes
he mostly just stands t...
[Thu Oct 2 11:43:44 2025] 📨 EVENT from wss://nos.lol
├── ID: e01356b1bf0a...
├── Pubkey: 0406b1bbbe74...
├── Kind: 1
├── Created: 1759419823
└── Content: ðŸ<C3B0>…ã®ãƒ«ãƒ†ã£ãƒ¼ã®ã¢XX
[Thu Oct 2 11:44:00 2025] 📨 EVENT from wss://nos.lol
├── ID: 5fad5c083762...
├── Pubkey: db0445869e55...
├── Kind: 1
├── Created: 1759419839
└── Content: Nothing but the best will do for magic internet money.
https://blossom.primal.net/5f7fbea898bfcf9...
[Thu Oct 2 11:44:10 2025] 📨 EVENT from wss://nos.lol
├── ID: 7cc57da68132...
├── Pubkey: 087fe3adb094...
├── Kind: 1
├── Created: 1759419850
└── Content: That is a classic film
[Thu Oct 2 11:44:15 2025] 📨 EVENT from wss://nos.lol
├── ID: 609a860530f5...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759419855
└── Content: ðŸ˜<C5B8>
Zurich?
[Thu Oct 2 11:44:16 2025] 📨 EVENT from wss://nos.lol
├── ID: 6af068b23493...
├── Pubkey: 2b1de1346ff1...
├── Kind: 1
├── Created: 1759419856
└── Content: Does your door work with a string? How is the door locked? Or can you still push it up?
[Thu Oct 2 11:44:23 2025] 📨 EVENT from wss://nos.lol
├── ID: 8ab3cd207956...
├── Pubkey: deab79dafa1c...
├── Kind: 1
├── Created: 1759419864
└── Content: I'll never understand why people believe folks who are paid to say others words professionally ðŸ...
[Thu Oct 2 11:44:31 2025] 📨 EVENT from wss://nos.lol
├── ID: d59a8d8dc608...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759419871
└── Content: sometimes he hangs out with the unnamed donkey made of moonlight
he has friends
[Thu Oct 2 11:44:32 2025] 📨 EVENT from wss://nos.lol
├── ID: 6c4978b1fce6...
├── Pubkey: c831e221f166...
├── Kind: 1
├── Created: 1759419872
└── Content: It was the Russian... but then again, maybe not 🤣
https://blossom.primal.net/986df1efadeff5...
[Thu Oct 2 11:44:34 2025] 📨 EVENT from wss://nos.lol
├── ID: 2a2f196a072c...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419872
└── Content: #meme #bitcoin
https://blossom.primal.net/813e03107f57f4606a2d8a8c129c6df03524fcdcbcdce6cbbf...
[Thu Oct 2 11:44:43 2025] 📨 EVENT from wss://nos.lol
├── ID: 9570719ae3ca...
├── Pubkey: f7922a0adb3f...
├── Kind: 1
├── Created: 1759419882
└── Content: This is the show that started all this #DeMu talk and its unlike anything you've ever listened to...
[Thu Oct 2 11:44:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 4c9de90e324d...
├── Pubkey: 16cb4b38fe9a...
├── Kind: 1
├── Created: 1759419893
└── Content: https://i.nostr.build/q6whqe8wlzMy6ubL.png
Its that time again! We're closing in on the end of th...
[Thu Oct 2 11:44:55 2025] 📨 EVENT from wss://nos.lol
├── ID: 3b5a6c87be19...
├── Pubkey: 036533caa872...
├── Kind: 1
├── Created: 1759419894
└── Content: Yeah! I like to think I don't save anything unnecessary, or totally unusable, but it happens. Let...
[Thu Oct 2 11:44:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 37bb53f99114...
├── Pubkey: b834a8c07a51...
├── Kind: 1
├── Created: 1759419897
└── Content: #Lafayette Indiana https://v.nostr.build/x3WmXwgR0CTEBJq9.mp4
[Thu Oct 2 11:45:00 2025] 📨 EVENT from wss://nos.lol
├── ID: 3a7898a12c5e...
├── Pubkey: d7c13d1edc3e...
├── Kind: 1
├── Created: 1759419900
└── Content: ✄------------ 0:45 ------------✄
[Thu Oct 2 11:45:01 2025] 📨 EVENT from wss://nos.lol
├── ID: 315bfab7c206...
├── Pubkey: 087fe3adb094...
├── Kind: 1
├── Created: 1759419901
└── Content: That is freaky
[Thu Oct 2 11:45:03 2025] 📨 EVENT from wss://nos.lol
├── ID: 6f06e3704059...
├── Pubkey: 35edf1096d3c...
├── Kind: 1
├── Created: 1759419902
└── Content: And how a normie is supposed to safeguard such an id from hacks? RFID under the skin?
[Thu Oct 2 11:45:03 2025] 📨 EVENT from wss://nos.lol
├── ID: 0ae20b5693f0...
├── Pubkey: ad0de68eb660...
├── Kind: 1
├── Created: 1759419904
└── Content: ความà¸<C3A0>ันà¸<C3A0>ับความหวังต่างà¸<C3A0>ันตรงไหà¸
#si...
[Thu Oct 2 11:45:05 2025] 📨 EVENT from wss://nos.lol
├── ID: a11231aea6b5...
├── Pubkey: f768fae9f239...
├── Kind: 1
├── Created: 1759419904
└── Content: #ãƒ<C3A3>ãºãƒ¯ãƒ¼ãƒ‰ãƒ©ãƒ³ã­ãƒ³ã°
1ä½<EFBFBD>: #リボ (11)
2ä½<EFBFBD>: #ã<>ªã<C2AA><C3A3> (5)
3ä½<EFBFBD>: #ミキサー (5)
4ä½...
[Thu Oct 2 11:45:10 2025] 📨 EVENT from wss://nos.lol
├── ID: fda1a94c635b...
├── Pubkey: bdb827d5dd18...
├── Kind: 1
├── Created: 1759419931
└── Content: Have been saying this for minimum 6-10 months now
[Thu Oct 2 11:45:15 2025] 📨 EVENT from wss://nos.lol
├── ID: fbbb1a8e1883...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419913
└── Content: #meme #bitcoin
https://blossom.primal.net/ea65f26f04fe282a56efc0c74b6f7881dcf95cf1bed76e9646...
[Thu Oct 2 11:45:18 2025] 📨 EVENT from wss://nos.lol
├── ID: dae37e3e2a01...
├── Pubkey: f683e87035f7...
├── Kind: 1
├── Created: 1759419916
└── Content: 📊 Reliable nostr statistics now available at npub.world
https://blossom.primal.net/fe6d77745c...
[Thu Oct 2 11:45:21 2025] 📨 EVENT from wss://nos.lol
├── ID: e98f692cd821...
├── Pubkey: 4cf2e85f2ecf...
├── Kind: 1
├── Created: 1759419902
└── Content: Market snapshot (2 Oct): PX 2,359.40 +0.33%; DAX 24,423.61 +1.29%; DJ STOXX 600 567.60 +0.52%; NA...
[Thu Oct 2 11:45:25 2025] 📨 EVENT from wss://nos.lol
├── ID: a4debf6eda9d...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759419924
└── Content: Well, nostr:npub1f5kc2agn63ecv2ua4909z9ahgmr2x9263na36jh6r908ql0926jq3nvk2u has done an amazing j...
[Thu Oct 2 11:45:36 2025] 📨 EVENT from wss://nos.lol
├── ID: 75edaab76e6a...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419934
└── Content: #meme #bitcoin
https://blossom.primal.net/47e112b56565c473db0300b9f1c8e9026d31f29b2e5a9f26b6...
[Thu Oct 2 11:45:42 2025] 📨 EVENT from wss://nos.lol
├── ID: cce964ee26c3...
├── Pubkey: 4e64c603aceb...
├── Kind: 1
├── Created: 1759419942
└── Content: ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ™<C5B8>ðŸ<C3B0>»
nostr:nevent1qqswaa533cykjc0vgxxmf8nmu2dg3g6uu0xdm5cw4...
[Thu Oct 2 11:45:43 2025] 📨 EVENT from wss://nos.lol
├── ID: bd66386bb0e4...
├── Pubkey: fdf22dc28791...
├── Kind: 1
├── Created: 1759419943
└── Content: https://image.nostr.build/ebf0ff9e9675e82b53718cf515dcb1168f03968fe0108993f43991559093c853.jpg
[Thu Oct 2 11:45:44 2025] 📨 EVENT from wss://nos.lol
├── ID: 3f1089f6f622...
├── Pubkey: adc14fa3ad59...
├── Kind: 1
├── Created: 1759419943
└── Content: 🫵ðŸ<C3B0>»ðŸ™<C5B8>
[Thu Oct 2 11:45:47 2025] 📨 EVENT from wss://nos.lol
├── ID: b2db18db5304...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419945
└── Content: #meme #bitcoin
https://blossom.primal.net/cb9eb04c5bb49d58a33c0e17425606f1e3357893cb6d47d3e0...
[Thu Oct 2 11:45:47 2025] 📨 EVENT from wss://nos.lol
├── ID: c0abb73c57da...
├── Pubkey: 96ae8563bd22...
├── Kind: 1
├── Created: 1759419947
└── Content: Health insurance is a scam.
[Thu Oct 2 11:45:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 25eaedc2c644...
├── Pubkey: d1f8ac7cfbac...
├── Kind: 1
├── Created: 1759419952
└── Content: Happy Belated Birthday! Hope you had a Funtastic day 🎉ðŸŽ<C5B8>ðŸ<C5B8>ðŸŽðŸŽŠ
[Thu Oct 2 11:45:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 3b6919f20ecb...
├── Pubkey: 52b4a076bcbb...
├── Kind: 1
├── Created: 1759419957
└── Content: gm Nostr.
[Thu Oct 2 11:45:58 2025] 📨 EVENT from wss://nos.lol
├── ID: d05a586a961a...
├── Pubkey: 71c20e5545c5...
├── Kind: 1
├── Created: 1759419957
└── Content: Mood every morning
https://blossom.primal.net/a4b9b97b0948f6ab25036c52ce3580db28dd52396f1f3d7277...
[Thu Oct 2 11:46:02 2025] 📨 EVENT from wss://nos.lol
├── ID: 93af19a07911...
├── Pubkey: f683e87035f7...
├── Kind: 1
├── Created: 1759419961
└── Content: cc nostr:nprofile1qy2hwumn8ghj7ct8vaezumn0wd68ytnvv9hxgqg4waehxw309a5xjum59ehx7um5wghxcctwvsqzpmn...
[Thu Oct 2 11:46:06 2025] 📨 EVENT from wss://nos.lol
├── ID: 92450741c3db...
├── Pubkey: 8384e79741c1...
├── Kind: 1
├── Created: 1759419967
└── Content: It looks like some one with Parkinson's made it.
[Thu Oct 2 11:46:16 2025] 📨 EVENT from wss://nos.lol
├── ID: f0b1fa224498...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419974
└── Content: #meme #bitcoin
BUY BITCOIN
https://blossom.primal.net/cc20ee3b13ecfc84fef02edf5c6bde6ee12431...
[Thu Oct 2 11:46:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 8b05cc12eb5c...
├── Pubkey: f40901c9f844...
├── Kind: 1
├── Created: 1759419979
└── Content: Pick a Random Open PR #2003
タイトル: Use nip22 style tags in git statuses
作æˆ<EFBFBD>者: dluvian...
[Thu Oct 2 11:46:23 2025] 📨 EVENT from wss://nos.lol
├── ID: e48131f4ad3e...
├── Pubkey: 899ab335d3b0...
├── Kind: 1
├── Created: 1759419983
└── Content: Bitcoin is invading trad-fi.
nostr:nevent1qqsxnxk6dc6gwthdd74em4tlrkecnyfg2zss3thdl390ja06qyhaf6g...
[Thu Oct 2 11:46:26 2025] 📨 EVENT from wss://nos.lol
├── ID: db3417a1ea38...
├── Pubkey: 4cf2e85f2ecf...
├── Kind: 1
├── Created: 1759419967
└── Content: Commerzbank’s latest forecasts suggest the US economy will grow this year and next slightly bel...
[Thu Oct 2 11:46:27 2025] 📨 EVENT from wss://nos.lol
├── ID: 9ef2f62d6b90...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759419986
└── Content: they, john and the no name donkey,
just kinda walk around or are still
ive never seen john get ...
[Thu Oct 2 11:46:28 2025] 📨 EVENT from wss://nos.lol
├── ID: 764def10463e...
├── Pubkey: f5e67a824944...
├── Kind: 1
├── Created: 1759419986
└── Content: compro mosquitos wolbachia ¿alguien?
[Thu Oct 2 11:46:33 2025] 📨 EVENT from wss://nos.lol
├── ID: 67c7dd1bcba2...
├── Pubkey: 04c960497af6...
├── Kind: 1
├── Created: 1759419993
└── Content: Oh, I didn't now we were orange , had a tail and ðŸ<C3B0>¾ðŸ<C3B0>¾ 🤔
Maybe I am delusional 🤔😅
...
[Thu Oct 2 11:46:36 2025] 📨 EVENT from wss://nos.lol
├── ID: afe890faef3e...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759419996
└── Content: https://npub.world/stats
🤌🤌
nostr:nevent1qqsd4cm78c4qrlskgp870qhh7qt9sepn98gp0utj2gk6v5j4...
[Thu Oct 2 11:46:51 2025] 📨 EVENT from wss://nos.lol
├── ID: 7acc74259799...
├── Pubkey: a3e4cba409d3...
├── Kind: 1
├── Created: 1759419989
└── Content:
https://uploads.postiz.com/3264e94fb1b846c8bfccf59e48d8bf85.png
[Thu Oct 2 11:46:58 2025] 📨 EVENT from wss://nos.lol
├── ID: 2fd4cd59789e...
├── Pubkey: 0b26f590631b...
├── Kind: 1
├── Created: 1759420017
└── Content: 🤣🤣🤣
[Thu Oct 2 11:47:01 2025] 📨 EVENT from wss://nos.lol
├── ID: 9c46471834c1...
├── Pubkey: 2b1de1346ff1...
├── Kind: 1
├── Created: 1759420021
└── Content: https://cdn.nostrcheck.me/1c674155c4f713054ec8a10df5eaa5636d243df40d07447980f1885642d706c1.webp
...
[Thu Oct 2 11:47:04 2025] 📨 EVENT from wss://nos.lol
├── ID: 51a4d7c2106c...
├── Pubkey: bc0bcc50f9a1...
├── Kind: 1
├── Created: 1759420023
└── Content: ألÙ<E2809E> مبروك 🥳 والله يرزقكم برهم وصلاحهم والحمدلله على ...
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: b63745941271...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759420018
└── Content: Gm agitator
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: ed874935ec1d...
├── Pubkey: 0406b1bbbe74...
├── Kind: 1
├── Created: 1759420025
└── Content: ã<>Šã„ã<E2809E>®ã<C2AE>™ðŸŒ”
https://youtu.be/bOSWJTu5Prw?si=oT3DtqbpbOp0VDrr
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: 0e32734cbc67...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420025
└── Content: https://image.nostr.build/7304775744fef9bc979a1940fb31202045b8b06f1ebfe90141266eeb28c52417.jpg
n...
[Thu Oct 2 11:47:21 2025] 📨 EVENT from wss://nos.lol
├── ID: 56acfa902e7d...
├── Pubkey: d981591e0ea6...
├── Kind: 1
├── Created: 1759419861
└── Content: DiversityWatch (October 2, 2025)
From Amerika.org
~~~ ADL Deletes ‘Extremism’ Database Afte...
[Thu Oct 2 11:47:25 2025] 📨 EVENT from wss://nos.lol
├── ID: faf15824f3bf...
├── Pubkey: deab79dafa1c...
├── Kind: 1
├── Created: 1759420047
└── Content: GM semi 🫡
[Thu Oct 2 11:47:32 2025] 📨 EVENT from wss://nos.lol
├── ID: ea997cd30e54...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420052
└── Content: firewood or foraged pennyroyal
but he doesnt work that much
i would never call him a beast of b...
[Thu Oct 2 11:47:38 2025] 📨 EVENT from wss://nos.lol
├── ID: fe973c7c45f5...
├── Pubkey: 0b26f590631b...
├── Kind: 1
├── Created: 1759420057
└── Content: Sorry brother 😬🤣 I was restarting router
[Thu Oct 2 11:47:39 2025] 📨 EVENT from wss://nos.lol
├── ID: f7f07c0a6a4d...
├── Pubkey: 356875ffd729...
├── Kind: 1
├── Created: 1759420059
└── Content: You want to get as far away from the lipid profile of seed oils as you can.
Terrible = canola oi...
[Thu Oct 2 11:47:51 2025] 📨 EVENT from wss://nos.lol
├── ID: d5af40b4e947...
├── Pubkey: edb470271297...
├── Kind: 1
├── Created: 1759420072
└── Content: 10 years ago I emerged from the woods to build something more
Now after having done so I feel t...
[Thu Oct 2 11:47:59 2025] 📨 EVENT from wss://nos.lol
├── ID: 4abc3d65f486...
├── Pubkey: af5e5c0f30b2...
├── Kind: 1
├── Created: 1759420078
└── Content: Dear me✨
Brave and beautiful one.
From now on and till the end of your earthly journey, I will...
[Thu Oct 2 11:48:01 2025] 📨 EVENT from wss://nos.lol
├── ID: f0f30b54f0ae...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759420079
└── Content: #meme #bitcoin
Ignore the direction of the school.
Keep stacking sats, fish.
https://blossom....
[Thu Oct 2 11:48:05 2025] 📨 EVENT from wss://nos.lol
├── ID: c5935c3e834b...
├── Pubkey: 3f770d65d3a7...
├── Kind: 1
├── Created: 1759420085
└── Content: Nice looking rooster, bro.
[Thu Oct 2 11:48:07 2025] 📨 EVENT from wss://nos.lol
├── ID: db0b8148c793...
├── Pubkey: a4132de3f6fe...
├── Kind: 1
├── Created: 1759420086
└── Content: Intercepted Gaza flotilla boats arrive at Israel’s Ashdod port
Several boats from the Global S...
[Thu Oct 2 11:48:09 2025] 📨 EVENT from wss://nos.lol
├── ID: a61672484b30...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420089
└── Content: thats what i know about john
he stands there and he sometimes has company
[Thu Oct 2 11:48:10 2025] 📨 EVENT from wss://nos.lol
├── ID: be57235ebb4b...
├── Pubkey: 01438c6f3044...
├── Kind: 1
├── Created: 1759419985
└── Content: Ex-OpenAI researcher dissects one of ChatGPT’s delusional spirals
https://techcrunch.com/2025/1...
[Thu Oct 2 11:48:10 2025] 📨 EVENT from wss://nos.lol
├── ID: 299ebf1ac3aa...
├── Pubkey: 0d211376b2c9...
├── Kind: 1
├── Created: 1759419938
└── Content: The best Amazon Prime Day deals on Anker charging gear and other accessories
https://www.engadget...
[Thu Oct 2 11:48:16 2025] 📨 EVENT from wss://nos.lol
├── ID: 1b9e8c4b3ee3...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420095
└── Content: ðŸ<C3B0>
nostr:nevent1qqsfc3j8rq6vr75c3k7xsmd945u49dfxwsshnh9z4pm2s27wl4ymjtcppamhxue69uhkumewwd68yt...
[Thu Oct 2 11:48:16 2025] 📨 EVENT from wss://nos.lol
├── ID: dbce08f706dd...
├── Pubkey: 36af108c2769...
├── Kind: 1
├── Created: 1759420096
└── Content: You don’t want your total cholesterol crazy high but don’t want it too low either. Mainstream...
[Thu Oct 2 11:48:42 2025] 📨 EVENT from wss://nos.lol
├── ID: a69f3dd53072...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759420119
└── Content: Well looks like I have to look back for that one
[Thu Oct 2 11:48:45 2025] 📨 EVENT from wss://nos.lol
├── ID: 8c11338af6bb...
├── Pubkey: 71c20e5545c5...
├── Kind: 1
├── Created: 1759420125
└── Content: mood every morning
https://blossom.primal.net/2de495138c52e3e71f19c437d4e2a83a78b215018dcf88f074...
[Thu Oct 2 11:48:55 2025] 📨 EVENT from wss://nos.lol
├── ID: 09c37f81cd31...
├── Pubkey: 8c5923931963...
├── Kind: 1
├── Created: 1759420132
└── Content: プラãƒ<C3A3>ナã<C5A0>«ã<C2AB>ªã<C2AA>£ã<C2A3>¦ã
[Thu Oct 2 11:48:56 2025] 📨 EVENT from wss://nos.lol
├── ID: 7d80f283a966...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420135
└── Content: ðŸ˜<C5B8>
nostr:nevent1qqsxkzjr6tmef5kxng3c4p4nk58lkmr2jm60y60jmszrha64fpnuy2sppamhxue69uhkumewwd68yt...
[Thu Oct 2 11:48:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 55927691ba22...
├── Pubkey: b63581fed371...
├── Kind: 1
├── Created: 1759420136
└── Content: â€<C3A2>Crypto Live Dealer Baccarat Redefines Online Gaming
The integration of blockchain technology ...
[Thu Oct 2 11:49:04 2025] 📨 EVENT from wss://nos.lol
├── ID: 230569d0c203...
├── Pubkey: 367cccfb659a...
├── Kind: 1
├── Created: 1759420143
└── Content: Original post: https://bsky.app/profile/did:plc:vrg5pyvxe6havmn5hiqqae22/post/3m274m4enuk2r https...
[Thu Oct 2 11:49:12 2025] 📨 EVENT from wss://nos.lol
├── ID: d9578c93625a...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420152
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta what's your favorit...
[Thu Oct 2 11:49:12 2025] 📨 EVENT from wss://nos.lol
├── ID: b8c5d604f68f...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420152
└── Content: If you have failed hard at birds I want to follow you
#farmstr
nostr:nevent1qqsqesh3dfqcyxmpf4p...
[Thu Oct 2 11:49:19 2025] 📨 EVENT from wss://nos.lol
├── ID: db5cb3126f16...
├── Pubkey: cb230a5e9341...
├── Kind: 1
├── Created: 1759420158
└── Content: ã<C3A3>ã<EFBFBD><C3A3>ã€<C3A3>プラãƒ<C3A3>ナã<C5A0>£ã<C2A3>¦ã<C2A6>™ã<E284A2>”ã<E2809D>„ã<E2809E>§ã<C2A7>™ã<E284A2>­ã€œï¼<C3AF>ã<EFBFBD>Šã<C3A3>ã<EFBFBD>§ã<C2A7>¨ã<C2A8>†ã<E280A0>”ã<E2809D>ã<E28093>„ã<E2809E>¾ã<C2BE>™ï¼<C3AF>ã<EFBFBD>©ã“ã<E2809C>ªã<C2AA>µã...
[Thu Oct 2 11:49:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 0b559f2c7aab...
├── Pubkey: bf2376e17ba4...
├── Kind: 1
├── Created: 1759420160
└── Content: funny that I haven’t seen people being unnecessarily mean about core/knots on nostr, or maybe I...
[Thu Oct 2 11:49:22 2025] 📨 EVENT from wss://nos.lol
├── ID: 225ec974be70...
├── Pubkey: d981591e0ea6...
├── Kind: 1
├── Created: 1759420041
└── Content: The Shadowy Legacy of Andrew McCabe
From Roger Stone
Andrew McCabe, former Deputy Director of t...
[Thu Oct 2 11:49:23 2025] 📨 EVENT from wss://nos.lol
├── ID: 6409e853ab14...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759420162
└── Content: It's a neverending paretto within the 20%.
[Thu Oct 2 11:49:32 2025] 📨 EVENT from wss://nos.lol
├── ID: 492c3b86c6b2...
├── Pubkey: 1848313553d3...
├── Kind: 1
├── Created: 1759420172
└── Content: Again, allowed yes. You should phrase your questions differently.
The answer to if we "should all...
[Thu Oct 2 11:49:39 2025] 📨 EVENT from wss://nos.lol
├── ID: 3df638b9b7bd...
├── Pubkey: 5bab615f042d...
├── Kind: 1
├── Created: 1759420178
└── Content: betaã<61>«äººæ¨©ã<C2A9>ªã<C2AA>„ã<E2809E>£ã<C2A3>¦ã<C2A6>„ã<E2809E><C3A3>ã‰ã<E280B0>©ã“ã<E2809C>•ã“ã<E2809C>«è¨€ã<C3A3>ãŒã<C592>Ÿã<C5B8>ã
[Thu Oct 2 11:49:44 2025] 📨 EVENT from wss://nos.lol
├── ID: 9fc1d1f3c429...
├── Pubkey: cf3a2db57981...
├── Kind: 1
├── Created: 1759420184
└── Content: Das ist nun die geputzte „Auslese“ für die erste richtige Pilzfanne in diesem Jahr 😋😋ð...
[Thu Oct 2 11:49:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 6b20ca81b8d9...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420193
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta can you hear me
[Thu Oct 2 11:49:54 2025] 📨 EVENT from wss://nos.lol
├── ID: a28bc5751cf3...
├── Pubkey: 479ec16d345e...
├── Kind: 1
├── Created: 1759420194
└── Content: คิดà¸à¸¶à¸‡à¹<C3A0>ชมà¸à¹Œà¹€à¸«à¸¡à¸·à¸­à¸™à¸<C3A0>ัน เจอà¸<C3A0>ันเชียงใ...
[Thu Oct 2 11:50:00 2025] 📨 EVENT from wss://nos.lol
├── ID: bb9570ecdbb2...
├── Pubkey: b1b4105a564a...
├── Kind: 1
├── Created: 1759420200
└── Content: ✄------------ 0:50 ------------✄
[Thu Oct 2 11:50:02 2025] 📨 EVENT from wss://nos.lol
├── ID: 877bfd84cb78...
├── Pubkey: 8c5923931963...
├── Kind: 1
├── Created: 1759420199
└── Content: ã<>Ÿã<C5B8>¶ã“有æ™ã<E284A2>®ã<C2AE>—ã<E28094>ãã<E2809A>¡ã‡ã<E280A1>£ã<C2A3>¨ã<C2A8>„ã<E2809E>„プランã<C2B3>˜ãƒã
[Thu Oct 2 11:50:08 2025] 📨 EVENT from wss://nos.lol
├── ID: d2a24f8bd654...
├── Pubkey: 832b77d5ecb0...
├── Kind: 1
├── Created: 1759420208
└── Content: 🟩BUY BTC with EUR
Price: 102390.56EUR (0%)
BTC: 0.023
EUR: 2355
Method: SEPA Instant
Created: ...
[Thu Oct 2 11:50:09 2025] 📨 EVENT from wss://nos.lol
├── ID: 4d617918bff3...
├── Pubkey: b7a07661869d...
├── Kind: 1
├── Created: 1759420209
└── Content: Le consensus de Nakamoto – Episode 22
Dans cette vidéo, Cyril Grunspan utilise à nouveau un m...
[Thu Oct 2 11:50:14 2025] 📨 EVENT from wss://nos.lol
├── ID: d60c3b59af2e...
├── Pubkey: ff9f3daff5dc...
├── Kind: 1
├── Created: 1759420210
└── Content: 🥞 ¡#PancakeSwap pulveriza su propio récord! El DEX multichain alcanzó $749 mil millones en ...
[Thu Oct 2 11:50:19 2025] 📨 EVENT from wss://nos.lol
├── ID: 347c25fe60e8...
├── Pubkey: 45835c36f41d...
├── Kind: 1
├── Created: 1759420218
└── Content: btw i use this for everything, i love it:
https://github.com/purifyjs/core
[Thu Oct 2 11:50:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 21c1e474706a...
├── Pubkey: 367cccfb659a...
├── Kind: 1
├── Created: 1759420219
└── Content: Original post: https://bsky.app/profile/did:plc:keg3c3lhpxiihjho4e7auzaq/post/3m27frfqb7225 https...
[Thu Oct 2 11:50:25 2025] 📨 EVENT from wss://nos.lol
├── ID: 540e8564dd09...
├── Pubkey: 5bab615f042d...
├── Kind: 1
├── Created: 1759420225
└── Content: 何ã<E280A2>ã<E280B9>—ã‰ã<E280B0>®ç„¡åˆ¶é™<C3A9>ã<EFBFBD>Œã<C592>¤ã<C2A4><C3A3>ã„ã<E2809E>¤
[Thu Oct 2 11:50:26 2025] 📨 EVENT from wss://nos.lol
├── ID: b40bae265610...
├── Pubkey: 957dd3687817...
├── Kind: 1
├── Created: 1759420216
└── Content: Jimmy Kimmel Calls Trump a 'Son of a Bitch,’ Says Government Shutdown Allows Him to ‘Say What...
[Thu Oct 2 11:50:27 2025] 📨 EVENT from wss://nos.lol
├── ID: 4d880eb2bdfb...
├── Pubkey: 5729ad991a7e...
├── Kind: 1
├── Created: 1759420226
└── Content: #asknostr how do you channel your creativity or excess energy?
Or when you feel low, how do you ...
[Thu Oct 2 11:50:30 2025] 📨 EVENT from wss://nos.lol
├── ID: 590e23e23d0f...
├── Pubkey: ded3db391584...
├── Kind: 1
├── Created: 1759420229
└── Content: insert mia martini
https://image.nostr.build/9d8ebd836f785a679932f89113809ee63fcbb70d05f31daf5bc...
[Thu Oct 2 11:50:30 2025] 📨 EVENT from wss://nos.lol
├── ID: 239892245953...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420230
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta hey
[Thu Oct 2 11:50:37 2025] 📨 EVENT from wss://nos.lol
├── ID: 19ec03e4b586...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420236
└── Content: Hulu Bender's New Look | Futurama | Hulu commercial
#Hulu #abancommercials #commercial Video Hulu...
[Thu Oct 2 11:50:38 2025] 📨 EVENT from wss://nos.lol
├── ID: 9575e4b84ac2...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420237
└── Content: john-knee?
johnny yea i know a johnny
like human john
not active friendship but no quarrel
[Thu Oct 2 11:50:47 2025] 📨 EVENT from wss://nos.lol
├── ID: 9ca71ff6759f...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420247
└── Content: Xbox Announcing new updates to Xbox Game Pass Ultimate commercial
#Xbox #abancommercials #commerc...
[Thu Oct 2 11:50:48 2025] 📨 EVENT from wss://nos.lol
├── ID: 95a8a2fc10cb...
├── Pubkey: edc615f59aa8...
├── Kind: 1
├── Created: 1759420247
└── Content: African what? Lol...
[Thu Oct 2 11:50:50 2025] 📨 EVENT from wss://nos.lol
├── ID: 6156127e4923...
├── Pubkey: 000001c66890...
├── Kind: 1
├── Created: 1759420249
└── Content: You think the neckline is bold? You should meet the attitude
https://images2.imgbox.com/bf/a0/90...
[Thu Oct 2 11:50:58 2025] 📨 EVENT from wss://nos.lol
├── ID: 92d07c3f1344...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420257
└── Content: How to save your progress in Ghost of Yotei
#GhostofYotei #GamingGuide #GameTips Learn
how to sa...

BIN
tests/pool_test Executable file

Binary file not shown.

679
tests/pool_test.c Normal file
View File

@ -0,0 +1,679 @@
/*
* Interactive Relay Pool Test Program
*
* Interactive command-line interface for testing nostr_relay_pool functionality.
* All output is logged to pool.log while the menu runs in the terminal.
*
* Usage: ./pool_test
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
// Global variables
volatile sig_atomic_t running = 1;
nostr_relay_pool_t* pool = NULL;
nostr_pool_subscription_t** subscriptions = NULL;
int subscription_count = 0;
int subscription_capacity = 0;
pthread_t poll_thread;
int log_fd = -1;
// Signal handler for clean shutdown
void signal_handler(int signum) {
(void)signum;
running = 0;
}
// Event callback - called when an event is received
void on_event(cJSON* event, const char* relay_url, void* user_data) {
(void)user_data;
// Extract basic event information
cJSON* id = cJSON_GetObjectItem(event, "id");
cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
cJSON* created_at = cJSON_GetObjectItem(event, "created_at");
cJSON* kind = cJSON_GetObjectItem(event, "kind");
cJSON* content = cJSON_GetObjectItem(event, "content");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0'; // Remove newline
dprintf(log_fd, "[%s] 📨 EVENT from %s\n", timestamp, relay_url);
dprintf(log_fd, "├── ID: %.12s...\n", id && cJSON_IsString(id) ? cJSON_GetStringValue(id) : "unknown");
dprintf(log_fd, "├── Pubkey: %.12s...\n", pubkey && cJSON_IsString(pubkey) ? cJSON_GetStringValue(pubkey) : "unknown");
dprintf(log_fd, "├── Kind: %d\n", kind && cJSON_IsNumber(kind) ? (int)cJSON_GetNumberValue(kind) : -1);
dprintf(log_fd, "├── Created: %lld\n", created_at && cJSON_IsNumber(created_at) ? (long long)cJSON_GetNumberValue(created_at) : 0);
// Truncate content if too long
if (content && cJSON_IsString(content)) {
const char* content_str = cJSON_GetStringValue(content);
size_t content_len = strlen(content_str);
if (content_len > 100) {
dprintf(log_fd, "└── Content: %.97s...\n", content_str);
} else {
dprintf(log_fd, "└── Content: %s\n", content_str);
}
} else {
dprintf(log_fd, "└── Content: (empty)\n");
}
dprintf(log_fd, "\n");
}
// EOSE callback - called when End of Stored Events is received
void on_eose(void* user_data) {
(void)user_data;
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 📋 EOSE received - all stored events delivered\n\n", timestamp);
}
// Background polling thread
void* poll_thread_func(void* arg) {
(void)arg;
while (running) {
if (pool) {
nostr_relay_pool_poll(pool, 100);
}
struct timespec ts = {0, 10000000}; // 10ms
nanosleep(&ts, NULL);
}
return NULL;
}
// Print menu
void print_menu() {
printf("\n=== NOSTR Relay Pool Test Menu ===\n");
printf("1. Start Pool (wss://relay.laantungir.net)\n");
printf("2. Stop Pool\n");
printf("3. Add relay to pool\n");
printf("4. Remove relay from pool\n");
printf("5. Add subscription\n");
printf("6. Remove subscription\n");
printf("7. Show pool status\n");
printf("8. Test reconnection (simulate disconnect)\n");
printf("9. Exit\n");
printf("Choice: ");
}
// Get user input with default
char* get_input(const char* prompt, const char* default_value) {
static char buffer[1024];
printf("%s", prompt);
if (default_value) {
printf(" [%s]", default_value);
}
printf(": ");
if (!fgets(buffer, sizeof(buffer), stdin)) {
return NULL;
}
// Remove newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
// Return default if empty
if (strlen(buffer) == 0 && default_value) {
return strdup(default_value);
}
return strdup(buffer);
}
// Parse comma-separated list into cJSON array
cJSON* parse_comma_list(const char* input, int is_number) {
if (!input || strlen(input) == 0) {
return NULL;
}
cJSON* array = cJSON_CreateArray();
if (!array) return NULL;
char* input_copy = strdup(input);
char* token = strtok(input_copy, ",");
while (token) {
// Trim whitespace
while (*token == ' ') token++;
char* end = token + strlen(token) - 1;
while (end > token && *end == ' ') *end-- = '\0';
if (is_number) {
int num = atoi(token);
cJSON_AddItemToArray(array, cJSON_CreateNumber(num));
} else {
cJSON_AddItemToArray(array, cJSON_CreateString(token));
}
token = strtok(NULL, ",");
}
free(input_copy);
return array;
}
// Add subscription interactively
void add_subscription() {
if (!pool) {
printf("❌ Pool not started\n");
return;
}
printf("\n--- Add Subscription ---\n");
printf("Enter filter values (press Enter for no value):\n");
cJSON* filter = cJSON_CreateObject();
// ids
char* ids_input = get_input("ids (comma-separated event ids)", NULL);
if (ids_input && strlen(ids_input) > 0) {
cJSON* ids = parse_comma_list(ids_input, 0);
if (ids) cJSON_AddItemToObject(filter, "ids", ids);
}
free(ids_input);
// authors
char* authors_input = get_input("authors (comma-separated pubkeys)", NULL);
if (authors_input && strlen(authors_input) > 0) {
cJSON* authors = parse_comma_list(authors_input, 0);
if (authors) cJSON_AddItemToObject(filter, "authors", authors);
}
free(authors_input);
// kinds
char* kinds_input = get_input("kinds (comma-separated numbers)", NULL);
if (kinds_input && strlen(kinds_input) > 0) {
cJSON* kinds = parse_comma_list(kinds_input, 1);
if (kinds) cJSON_AddItemToObject(filter, "kinds", kinds);
}
free(kinds_input);
// #e tag
char* e_input = get_input("#e (comma-separated event ids)", NULL);
if (e_input && strlen(e_input) > 0) {
cJSON* e_array = parse_comma_list(e_input, 0);
if (e_array) cJSON_AddItemToObject(filter, "#e", e_array);
}
free(e_input);
// #p tag
char* p_input = get_input("#p (comma-separated pubkeys)", NULL);
if (p_input && strlen(p_input) > 0) {
cJSON* p_array = parse_comma_list(p_input, 0);
if (p_array) cJSON_AddItemToObject(filter, "#p", p_array);
}
free(p_input);
// since
char* since_input = get_input("since (unix timestamp or 'n' for now)", NULL);
if (since_input && strlen(since_input) > 0) {
if (strcmp(since_input, "n") == 0) {
// Use current timestamp
time_t now = time(NULL);
cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber((int)now));
printf("Using current timestamp: %ld\n", now);
} else {
int since = atoi(since_input);
if (since > 0) cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since));
}
}
free(since_input);
// until
char* until_input = get_input("until (unix timestamp)", NULL);
if (until_input && strlen(until_input) > 0) {
int until = atoi(until_input);
if (until > 0) cJSON_AddItemToObject(filter, "until", cJSON_CreateNumber(until));
}
free(until_input);
// limit
char* limit_input = get_input("limit (max events)", "10");
if (limit_input && strlen(limit_input) > 0) {
int limit = atoi(limit_input);
if (limit > 0) cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(limit));
}
free(limit_input);
// Get relay URLs from pool
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
cJSON_Delete(filter);
return;
}
// Ask about close_on_eose behavior
char* close_input = get_input("Close subscription on EOSE? (y/n)", "n");
int close_on_eose = (close_input && strcmp(close_input, "y") == 0) ? 1 : 0;
free(close_input);
// Create subscription
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
pool,
(const char**)relay_urls,
relay_count,
filter,
on_event,
on_eose,
NULL,
close_on_eose
);
// Free relay URLs
for (int i = 0; i < relay_count; i++) {
free(relay_urls[i]);
}
free(relay_urls);
free(statuses);
if (!sub) {
printf("❌ Failed to create subscription\n");
cJSON_Delete(filter);
return;
}
// Store subscription
if (subscription_count >= subscription_capacity) {
subscription_capacity = subscription_capacity == 0 ? 10 : subscription_capacity * 2;
subscriptions = realloc(subscriptions, subscription_capacity * sizeof(nostr_pool_subscription_t*));
}
subscriptions[subscription_count++] = sub;
printf("✅ Subscription created (ID: %d)\n", subscription_count);
// Log the filter
char* filter_json = cJSON_Print(filter);
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🔍 New subscription created (ID: %d)\n", timestamp, subscription_count);
dprintf(log_fd, "Filter: %s\n\n", filter_json);
free(filter_json);
}
// Remove subscription
void remove_subscription() {
if (subscription_count == 0) {
printf("❌ No subscriptions to remove\n");
return;
}
printf("\n--- Remove Subscription ---\n");
printf("Available subscriptions:\n");
for (int i = 0; i < subscription_count; i++) {
printf("%d. Subscription %d\n", i + 1, i + 1);
}
char* choice_input = get_input("Enter subscription number to remove", NULL);
if (!choice_input || strlen(choice_input) == 0) {
free(choice_input);
return;
}
int choice = atoi(choice_input) - 1;
free(choice_input);
if (choice < 0 || choice >= subscription_count) {
printf("❌ Invalid subscription number\n");
return;
}
nostr_pool_subscription_close(subscriptions[choice]);
// Shift remaining subscriptions
for (int i = choice; i < subscription_count - 1; i++) {
subscriptions[i] = subscriptions[i + 1];
}
subscription_count--;
printf("✅ Subscription removed\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🗑️ Subscription removed (was ID: %d)\n\n", timestamp, choice + 1);
}
// Show pool status
void show_pool_status() {
if (!pool) {
printf("❌ Pool not started\n");
return;
}
// Give polling thread time to establish connections
printf("⏳ Waiting for connections to establish...\n");
sleep(3);
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
printf("\n📊 POOL STATUS\n");
printf("Relays: %d\n", relay_count);
printf("Subscriptions: %d\n", subscription_count);
if (relay_count > 0) {
printf("\nRelay Details:\n");
for (int i = 0; i < relay_count; i++) {
const char* status_str;
switch (statuses[i]) {
case NOSTR_POOL_RELAY_CONNECTED: status_str = "🟢 CONNECTED"; break;
case NOSTR_POOL_RELAY_CONNECTING: status_str = "🟡 CONNECTING"; break;
case NOSTR_POOL_RELAY_DISCONNECTED: status_str = "⚪ DISCONNECTED"; break;
case NOSTR_POOL_RELAY_ERROR: status_str = "🔴 ERROR"; break;
default: status_str = "❓ UNKNOWN"; break;
}
printf("├── %s: %s\n", relay_urls[i], status_str);
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_urls[i]);
if (stats) {
printf("│ ├── Events received: %d\n", stats->events_received);
printf("│ ├── Connection attempts: %d\n", stats->connection_attempts);
printf("│ ├── Connection failures: %d\n", stats->connection_failures);
printf("│ ├── Ping latency: %.2f ms\n", stats->ping_latency_current);
printf("│ └── Query latency: %.2f ms\n", stats->query_latency_avg);
}
free(relay_urls[i]);
}
free(relay_urls);
free(statuses);
}
printf("\n");
}
int main() {
// Setup logging to file
log_fd = open("pool.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (log_fd == -1) {
fprintf(stderr, "❌ Failed to open pool.log for writing\n");
return 1;
}
// Initialize NOSTR library
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "❌ Failed to initialize NOSTR library\n");
close(log_fd);
return 1;
}
// Setup signal handler
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Start polling thread
if (pthread_create(&poll_thread, NULL, poll_thread_func, NULL) != 0) {
fprintf(stderr, "❌ Failed to create polling thread\n");
nostr_cleanup();
close(log_fd);
return 1;
}
printf("🔗 NOSTR Relay Pool Interactive Test\n");
printf("=====================================\n");
printf("All event output is logged to pool.log\n");
printf("Press Ctrl+C to exit\n\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🚀 Pool test started\n\n", timestamp);
// Main menu loop
while (running) {
print_menu();
char choice;
if (scanf("%c", &choice) != 1) {
break;
}
// Consume newline
int c;
while ((c = getchar()) != '\n' && c != EOF);
switch (choice) {
case '1': { // Start Pool
if (pool) {
printf("❌ Pool already started\n");
break;
}
// Create pool with custom reconnection configuration for faster testing
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
config->ping_interval_seconds = 5; // Ping every 5 seconds for testing
pool = nostr_relay_pool_create(config);
if (!pool) {
printf("❌ Failed to create pool\n");
break;
}
if (nostr_relay_pool_add_relay(pool, "wss://relay.laantungir.net") != NOSTR_SUCCESS) {
printf("❌ Failed to add default relay\n");
nostr_relay_pool_destroy(pool);
pool = NULL;
break;
}
printf("✅ Pool started with wss://relay.laantungir.net\n");
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🏊 Pool started with default relay\n\n", timestamp);
break;
}
case '2': { // Stop Pool
if (!pool) {
printf("❌ Pool not started\n");
break;
}
// Close all subscriptions
for (int i = 0; i < subscription_count; i++) {
if (subscriptions[i]) {
nostr_pool_subscription_close(subscriptions[i]);
}
}
free(subscriptions);
subscriptions = NULL;
subscription_count = 0;
subscription_capacity = 0;
nostr_relay_pool_destroy(pool);
pool = NULL;
printf("✅ Pool stopped\n");
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🛑 Pool stopped\n\n", timestamp);
break;
}
case '3': { // Add relay
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char* url = get_input("Enter relay URL", "wss://relay.example.com");
if (url && strlen(url) > 0) {
if (nostr_relay_pool_add_relay(pool, url) == NOSTR_SUCCESS) {
printf("✅ Relay added: %s\n", url);
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] Relay added: %s\n\n", timestamp, url);
} else {
printf("❌ Failed to add relay\n");
}
}
free(url);
break;
}
case '4': { // Remove relay
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char* url = get_input("Enter relay URL to remove", NULL);
if (url && strlen(url) > 0) {
if (nostr_relay_pool_remove_relay(pool, url) == NOSTR_SUCCESS) {
printf("✅ Relay removed: %s\n", url);
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] Relay removed: %s\n\n", timestamp, url);
} else {
printf("❌ Failed to remove relay\n");
}
}
free(url);
break;
}
case '5': // Add subscription
add_subscription();
break;
case '6': // Remove subscription
remove_subscription();
break;
case '7': // Show status
show_pool_status();
break;
case '8': { // Test reconnection
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
break;
}
printf("\n--- Test Reconnection ---\n");
printf("Available relays:\n");
for (int i = 0; i < relay_count; i++) {
printf("%d. %s (%s)\n", i + 1, relay_urls[i],
statuses[i] == NOSTR_POOL_RELAY_CONNECTED ? "CONNECTED" : "NOT CONNECTED");
}
char* choice_input = get_input("Enter relay number to test reconnection with", NULL);
if (!choice_input || strlen(choice_input) == 0) {
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
free(choice_input);
break;
}
int choice = atoi(choice_input) - 1;
free(choice_input);
if (choice < 0 || choice >= relay_count) {
printf("❌ Invalid relay number\n");
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
break;
}
printf("🔄 Testing reconnection with %s...\n", relay_urls[choice]);
printf(" The pool is configured with automatic reconnection enabled.\n");
printf(" If the connection drops, it will automatically attempt to reconnect\n");
printf(" with exponential backoff (1s → 2s → 4s → 8s → 16s → 30s max).\n");
printf(" Connection health is monitored with ping/pong every 30 seconds.\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🔄 TEST: Testing reconnection behavior with %s\n", timestamp, relay_urls[choice]);
dprintf(log_fd, " Pool configured with: auto-reconnect=ON, max_attempts=10, ping_interval=30s\n\n");
printf("✅ Reconnection test initiated. Monitor the status and logs for reconnection activity.\n");
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
break;
}
case '9': // Exit
running = 0;
break;
default:
printf("❌ Invalid choice\n");
break;
}
}
printf("\n🧹 Cleaning up...\n");
// Stop polling thread
running = 0;
pthread_join(poll_thread, NULL);
// Clean up pool and subscriptions
if (pool) {
for (int i = 0; i < subscription_count; i++) {
if (subscriptions[i]) {
nostr_pool_subscription_close(subscriptions[i]);
}
}
free(subscriptions);
nostr_relay_pool_destroy(pool);
printf("✅ Pool destroyed\n");
}
// Cleanup
nostr_cleanup();
close(log_fd);
printf("👋 Test completed\n");
return 0;
}

BIN
tests/relay_synchronous_test Executable file

Binary file not shown.

View File

@ -0,0 +1,242 @@
/*
* Relay Pool Test Program
*
* Tests the nostr_relay_pool functionality with persistent connections
* and subscriptions. Prints events as they arrive and shows connection status.
*
* Usage: ./pool_test
* Press Ctrl+C to exit
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
// Global variables for signal handling
volatile sig_atomic_t running = 1;
time_t last_status_time = 0;
// Signal handler for clean shutdown
void signal_handler(int signum) {
(void)signum; // Suppress unused parameter warning
printf("\n🛑 Received signal, shutting down...\n");
running = 0;
}
// Event callback - called when an event is received
void on_event(cJSON* event, const char* relay_url, void* user_data) {
(void)user_data; // Suppress unused parameter warning
// Extract basic event information
cJSON* id = cJSON_GetObjectItem(event, "id");
cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
cJSON* created_at = cJSON_GetObjectItem(event, "created_at");
cJSON* kind = cJSON_GetObjectItem(event, "kind");
cJSON* content = cJSON_GetObjectItem(event, "content");
printf("\n📨 EVENT from %s\n", relay_url);
printf("├── ID: %.12s...\n", id && cJSON_IsString(id) ? cJSON_GetStringValue(id) : "unknown");
printf("├── Pubkey: %.12s...\n", pubkey && cJSON_IsString(pubkey) ? cJSON_GetStringValue(pubkey) : "unknown");
printf("├── Kind: %d\n", kind && cJSON_IsNumber(kind) ? (int)cJSON_GetNumberValue(kind) : -1);
printf("├── Created: %lld\n", created_at && cJSON_IsNumber(created_at) ? (long long)cJSON_GetNumberValue(created_at) : 0);
// Truncate content if too long
if (content && cJSON_IsString(content)) {
const char* content_str = cJSON_GetStringValue(content);
size_t content_len = strlen(content_str);
if (content_len > 100) {
printf("└── Content: %.97s...\n", content_str);
} else {
printf("└── Content: %s\n", content_str);
}
} else {
printf("└── Content: (empty)\n");
}
fflush(stdout);
}
// EOSE callback - called when End of Stored Events is received
void on_eose(void* user_data) {
(void)user_data; // Suppress unused parameter warning
printf("📋 EOSE received - all stored events delivered\n");
fflush(stdout);
}
// Print connection status for all relays
void print_relay_status(nostr_relay_pool_t* pool) {
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
return;
}
printf("\n📊 RELAY STATUS (%d relays):\n", relay_count);
for (int i = 0; i < relay_count; i++) {
const char* status_str;
switch (statuses[i]) {
case NOSTR_POOL_RELAY_CONNECTED:
status_str = "🟢 CONNECTED";
break;
case NOSTR_POOL_RELAY_CONNECTING:
status_str = "🟡 CONNECTING";
break;
case NOSTR_POOL_RELAY_DISCONNECTED:
status_str = "⚪ DISCONNECTED";
break;
case NOSTR_POOL_RELAY_ERROR:
status_str = "🔴 ERROR";
break;
default:
status_str = "❓ UNKNOWN";
break;
}
printf("├── %s: %s\n", relay_urls[i], status_str);
// Show additional stats if available
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_urls[i]);
if (stats) {
printf("│ ├── Events received: %d\n", stats->events_received);
printf("│ ├── Connection attempts: %d\n", stats->connection_attempts);
printf("│ └── Connection failures: %d\n", stats->connection_failures);
}
free(relay_urls[i]);
}
printf("\n");
free(relay_urls);
free(statuses);
fflush(stdout);
}
int main() {
printf("🔗 NOSTR Relay Pool Test\n");
printf("========================\n");
printf("Testing persistent relay connections with subscriptions.\n");
printf("Press Ctrl+C to exit.\n\n");
// Initialize NOSTR library
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "❌ Failed to initialize NOSTR library\n");
return 1;
}
// Setup signal handler for clean shutdown
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Create relay pool with default configuration
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
nostr_relay_pool_t* pool = nostr_relay_pool_create(config);
if (!pool) {
fprintf(stderr, "❌ Failed to create relay pool\n");
nostr_cleanup();
return 1;
}
// Add relays to the pool
const char* relay_urls[] = {
"wss://nostr.mom",
"wss://relay.laantungir.net",
"wss://nos.lol"
};
int relay_count = 3;
printf("📡 Adding %d relays to pool:\n", relay_count);
for (int i = 0; i < relay_count; i++) {
printf("├── %s\n", relay_urls[i]);
if (nostr_relay_pool_add_relay(pool, relay_urls[i]) != NOSTR_SUCCESS) {
printf("│ ❌ Failed to add relay\n");
} else {
printf("│ ✅ Added successfully\n");
}
}
printf("\n");
// Create filter for subscription (kind 1 events - text notes)
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(10)); // Limit to 10 events per relay
printf("🔍 Creating subscription with filter:\n");
char* filter_json = cJSON_Print(filter);
printf("%s\n\n", filter_json);
free(filter_json);
// Create subscription
nostr_pool_subscription_t* subscription = nostr_relay_pool_subscribe(
pool,
relay_urls,
relay_count,
filter,
on_event, // Event callback
on_eose, // EOSE callback
NULL, // User data (not used)
0 // close_on_eose (false - keep subscription open)
);
if (!subscription) {
fprintf(stderr, "❌ Failed to create subscription\n");
cJSON_Delete(filter);
nostr_relay_pool_destroy(pool);
nostr_cleanup();
return 1;
}
printf("✅ Subscription created successfully\n");
printf("🎯 Listening for events... (Ctrl+C to exit)\n\n");
// Record start time for status updates
last_status_time = time(NULL);
// Main event loop
while (running) {
// Poll for events (100ms timeout)
int events_processed = nostr_relay_pool_poll(pool, 100);
// Check if we should print status (every 30 seconds)
time_t current_time = time(NULL);
if (current_time - last_status_time >= 30) {
print_relay_status(pool);
last_status_time = current_time;
}
// Small delay to prevent busy waiting
if (events_processed == 0) {
struct timespec ts = {0, 10000000}; // 10ms
nanosleep(&ts, NULL);
}
}
printf("\n🧹 Cleaning up...\n");
// Close subscription
if (subscription) {
nostr_pool_subscription_close(subscription);
printf("✅ Subscription closed\n");
}
// Destroy pool
nostr_relay_pool_destroy(pool);
printf("✅ Relay pool destroyed\n");
// Cleanup JSON
cJSON_Delete(filter);
// Cleanup library
nostr_cleanup();
printf("👋 Test completed successfully\n");
return 0;
}

Binary file not shown.

View File

@ -10,7 +10,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include "../nostr_core/nostr_common.h" #include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h" #include "../cjson/cJSON.h"
@ -80,7 +80,7 @@ int main() {
const char* filter_json = const char* filter_json =
"{" "{"
" \"kinds\": [1]," " \"kinds\": [1],"
" \"limit\": 1" " \"limit\": 4"
"}"; "}";
// Alternative filter examples (comment out the one above, uncomment one below): // Alternative filter examples (comment out the one above, uncomment one below):
@ -133,7 +133,8 @@ int main() {
cJSON** results = synchronous_query_relays_with_progress( cJSON** results = synchronous_query_relays_with_progress(
test_relays, relay_count, filter, test_mode, test_relays, relay_count, filter, test_mode,
&result_count, 5, progress_callback, NULL &result_count, 5, progress_callback, NULL,
1, NULL // nip42_enabled = true, private_key = NULL (no auth)
); );
time_t end_time = time(NULL); time_t end_time = time(NULL);

Binary file not shown.

View File

@ -1,103 +0,0 @@
/*
* WebSocket SSL Test - Test OpenSSL WebSocket implementation
* Connect to a NOSTR relay and fetch one type 1 event
*/
#include <stdio.h>
#include <stdlib.h>
#include "../cjson/cJSON.h"
#include "../nostr_core/nostr_common.h"
// Progress callback to show connection status
static void progress_callback(
const char* relay_url,
const char* status,
const char* event_id,
int events_received,
int total_relays,
int completed_relays,
void* user_data)
{
printf("Progress: %s - %s", relay_url ? relay_url : "Summary", status);
if (event_id) {
printf(" (Event: %.12s...)", event_id);
}
printf(" [%d/%d events, %d/%d relays]\n",
events_received, *(int*)user_data, completed_relays, total_relays);
}
int main() {
printf("WebSocket SSL Test - Testing OpenSSL WebSocket with NOSTR relay\n");
printf("================================================================\n");
// Initialize NOSTR library
if (nostr_init() != NOSTR_SUCCESS) {
printf("❌ Failed to initialize NOSTR library\n");
return 1;
}
printf("✅ NOSTR library initialized\n");
// Setup relay and filter
const char* relay_urls[] = {"wss://nostr.mom"};
int relay_count = 1;
// Create filter for type 1 events (text notes), limit to 1 event
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(1));
printf("📡 Connecting to %s...\n", relay_urls[0]);
printf("🔍 Requesting 1 type 1 event (text note)...\n\n");
// Query the relay
int result_count = 0;
int expected_events = 1;
cJSON** events = synchronous_query_relays_with_progress(
relay_urls,
relay_count,
filter,
RELAY_QUERY_FIRST_RESULT, // Return as soon as we get the first event
&result_count,
10, // 10 second timeout
progress_callback,
&expected_events
);
// Process results
if (events && result_count > 0) {
printf("\n✅ Successfully received %d event(s)!\n", result_count);
printf("📄 Raw JSON event data:\n");
printf("========================\n");
for (int i = 0; i < result_count; i++) {
char* json_string = cJSON_Print(events[i]);
if (json_string) {
printf("%s\n\n", json_string);
free(json_string);
}
cJSON_Delete(events[i]);
}
free(events);
printf("🎉 WebSocket SSL Test PASSED - OpenSSL WebSocket working correctly!\n");
} else {
printf("\n❌ No events received or query failed\n");
printf("❌ WebSocket SSL Test FAILED\n");
// Cleanup and return error
cJSON_Delete(filter);
nostr_cleanup();
return 1;
}
// Cleanup
cJSON_Delete(filter);
nostr_cleanup();
printf("✅ WebSocket connection and TLS working with OpenSSL\n");
return 0;
}