nostr_core_lib/POOL_API.md

22 KiB

Relay Pool API Reference

This document describes the public API for the Nostr Relay Pool implementation in core_relay_pool.c.

Function Summary

Function Description
nostr_relay_pool_create() Create and initialize a new relay pool
nostr_relay_pool_destroy() Destroy pool and cleanup all resources
nostr_relay_pool_add_relay() Add a relay URL to the pool
nostr_relay_pool_remove_relay() Remove a relay URL from the pool
nostr_relay_pool_subscribe() Create async subscription with callbacks
nostr_pool_subscription_close() Close subscription and free resources
nostr_relay_pool_run() Run event loop for specified timeout
nostr_relay_pool_poll() Single iteration poll and dispatch
nostr_relay_pool_query_sync() Synchronous query returning event array
nostr_relay_pool_get_event() Get single most recent event
nostr_relay_pool_publish() Publish event and wait for OK responses
nostr_relay_pool_get_relay_status() Get connection status for a relay
nostr_relay_pool_list_relays() List all relays and their statuses
nostr_relay_pool_get_relay_stats() Get detailed statistics for a relay
nostr_relay_pool_reset_relay_stats() Reset statistics for a relay
nostr_relay_pool_get_relay_query_latency() Get average query latency for a relay

Pool Lifecycle

Create Pool

Function: nostr_relay_pool_create()

nostr_relay_pool_t* nostr_relay_pool_create(void);

Example:

#include "nostr_core.h"

int main() {
    // Create a new relay pool
    nostr_relay_pool_t* pool = nostr_relay_pool_create();
    if (!pool) {
        fprintf(stderr, "Failed to create relay pool\n");
        return -1;
    }
    
    // Use the pool...
    
    // Clean up
    nostr_relay_pool_destroy(pool);
    return 0;
}

Destroy Pool

Function: nostr_relay_pool_destroy()

void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);

Example:

// Properly cleanup a relay pool
void cleanup_pool(nostr_relay_pool_t* pool) {
    if (pool) {
        // This will close all active subscriptions and relay connections
        nostr_relay_pool_destroy(pool);
        pool = NULL;
    }
}

Relay Management

Add Relay

Function: nostr_relay_pool_add_relay()

int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);

Example:

int setup_relays(nostr_relay_pool_t* pool) {
    const char* relays[] = {
        "wss://relay.damus.io",
        "wss://nos.lol",
        "wss://relay.nostr.band"
    };
    
    for (int i = 0; i < 3; i++) {
        int result = nostr_relay_pool_add_relay(pool, relays[i]);
        if (result != NOSTR_SUCCESS) {
            fprintf(stderr, "Failed to add relay %s: %d\n", relays[i], result);
            return -1;
        }
        printf("Added relay: %s\n", relays[i]);
    }
    
    return 0;
}

Remove Relay

Function: nostr_relay_pool_remove_relay()

int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);

Example:

int remove_slow_relay(nostr_relay_pool_t* pool) {
    const char* slow_relay = "wss://slow-relay.example.com";
    
    int result = nostr_relay_pool_remove_relay(pool, slow_relay);
    if (result == NOSTR_SUCCESS) {
        printf("Successfully removed relay: %s\n", slow_relay);
    } else {
        printf("Failed to remove relay %s (may not exist)\n", slow_relay);
    }
    
    return result;
}

Subscriptions (Asynchronous)

Subscribe to Events

Function: 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,
    void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
    void (*on_eose)(void* user_data),
    void* user_data);

Example:

#include "cjson/cJSON.h"

// Event callback - called for each received event
void handle_event(cJSON* event, const char* relay_url, void* user_data) {
    cJSON* content = cJSON_GetObjectItem(event, "content");
    cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
    
    if (content && pubkey) {
        printf("Event from %s: %s (by %s)\n", 
               relay_url, 
               cJSON_GetStringValue(content),
               cJSON_GetStringValue(pubkey));
    }
}

// EOSE callback - called when all relays finish sending stored events
void handle_eose(void* user_data) {
    printf("All relays finished sending stored events\n");
}

int subscribe_to_notes(nostr_relay_pool_t* pool) {
    // Create filter for kind 1 (text notes) from last hour
    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    
    time_t since = time(NULL) - 3600; // Last hour
    cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since));
    cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(50));
    
    // Subscribe to specific relays
    const char* relay_urls[] = {
        "wss://relay.damus.io",
        "wss://nos.lol"
    };
    
    nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
        pool,
        relay_urls,
        2,
        filter,
        handle_event,
        handle_eose,
        NULL  // user_data
    );
    
    cJSON_Delete(filter); // Pool makes its own copy
    
    if (!sub) {
        fprintf(stderr, "Failed to create subscription\n");
        return -1;
    }
    
    // Drive the event loop to receive events
    printf("Listening for events for 30 seconds...\n");
    nostr_relay_pool_run(pool, 30000); // 30 seconds
    
    // Close subscription
    nostr_pool_subscription_close(sub);
    return 0;
}

Close Subscription

Function: nostr_pool_subscription_close()

int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);

Example:

// Subscription management with cleanup
typedef struct {
    nostr_pool_subscription_t* subscription;
    int event_count;
    int should_stop;
} subscription_context_t;

void event_counter(cJSON* event, const char* relay_url, void* user_data) {
    subscription_context_t* ctx = (subscription_context_t*)user_data;
    ctx->event_count++;
    
    printf("Received event #%d from %s\n", ctx->event_count, relay_url);
    
    // Stop after 10 events
    if (ctx->event_count >= 10) {
        ctx->should_stop = 1;
    }
}

int limited_subscription(nostr_relay_pool_t* pool) {
    subscription_context_t ctx = {0};
    
    // Create filter
    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    
    const char* relay_urls[] = {"wss://relay.damus.io"};
    
    ctx.subscription = nostr_relay_pool_subscribe(
        pool, relay_urls, 1, filter, event_counter, NULL, &ctx);
    
    cJSON_Delete(filter);
    
    if (!ctx.subscription) {
        return -1;
    }
    
    // Poll until we should stop
    while (!ctx.should_stop) {
        int events = nostr_relay_pool_poll(pool, 100);
        if (events < 0) break;
    }
    
    // Clean up
    int result = nostr_pool_subscription_close(ctx.subscription);
    printf("Subscription closed with result: %d\n", result);
    
    return 0;
}

Event Loop

Run Timed Loop

Function: nostr_relay_pool_run()

int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);

Example:

int run_event_loop(nostr_relay_pool_t* pool) {
    printf("Starting event loop for 60 seconds...\n");
    
    // Run for 60 seconds, processing all incoming events
    int total_events = nostr_relay_pool_run(pool, 60000);
    
    if (total_events < 0) {
        fprintf(stderr, "Event loop error\n");
        return -1;
    }
    
    printf("Processed %d events total\n", total_events);
    return 0;
}

Single Poll Iteration

Function: nostr_relay_pool_poll()

int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);

Example:

// Integration with custom main loop
int custom_main_loop(nostr_relay_pool_t* pool) {
    int running = 1;
    int total_events = 0;
    
    while (running) {
        // Poll for Nostr events (non-blocking with 50ms timeout)
        int events = nostr_relay_pool_poll(pool, 50);
        if (events > 0) {
            total_events += events;
            printf("Processed %d events this iteration\n", events);
        }
        
        // Do other work in your application
        // handle_ui_events();
        // process_background_tasks();
        
        // Check exit condition
        // running = !should_exit();
        
        // Simple exit after 100 events for demo
        if (total_events >= 100) {
            running = 0;
        }
    }
    
    printf("Main loop finished, processed %d total events\n", total_events);
    return 0;
}

Synchronous Operations

Query Multiple Events

Function: nostr_relay_pool_query_sync()

cJSON** nostr_relay_pool_query_sync(
    nostr_relay_pool_t* pool,
    const char** relay_urls,
    int relay_count, 
    cJSON* filter,
    int* event_count,
    int timeout_ms);

Example:

int query_recent_notes(nostr_relay_pool_t* pool) {
    // Create filter for recent 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(20));
    
    const char* relay_urls[] = {
        "wss://relay.damus.io",
        "wss://nos.lol"
    };
    
    int event_count = 0;
    cJSON** events = nostr_relay_pool_query_sync(
        pool, relay_urls, 2, filter, &event_count, 10000); // 10 second timeout
    
    cJSON_Delete(filter);
    
    if (!events) {
        printf("No events received or query failed\n");
        return -1;
    }
    
    printf("Received %d events:\n", event_count);
    for (int i = 0; i < event_count; i++) {
        cJSON* content = cJSON_GetObjectItem(events[i], "content");
        if (content) {
            printf("  %d: %s\n", i + 1, cJSON_GetStringValue(content));
        }
        
        // Free each event
        cJSON_Delete(events[i]);
    }
    
    // Free the events array
    free(events);
    return event_count;
}

Get Single Most Recent Event

Function: nostr_relay_pool_get_event()

cJSON* nostr_relay_pool_get_event(
    nostr_relay_pool_t* pool,
    const char** relay_urls,
    int relay_count,
    cJSON* filter,
    int timeout_ms);

Example:

int get_latest_note_from_pubkey(nostr_relay_pool_t* pool, const char* pubkey_hex) {
    // Create filter for specific author's notes
    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    
    cJSON* authors = cJSON_CreateArray();
    cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
    cJSON_AddItemToObject(filter, "authors", authors);
    
    cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(1));
    
    const char* relay_urls[] = {"wss://relay.damus.io"};
    
    cJSON* event = nostr_relay_pool_get_event(
        pool, relay_urls, 1, filter, 5000); // 5 second timeout
    
    cJSON_Delete(filter);
    
    if (!event) {
        printf("No recent event found for pubkey %s\n", pubkey_hex);
        return -1;
    }
    
    cJSON* content = cJSON_GetObjectItem(event, "content");
    cJSON* created_at = cJSON_GetObjectItem(event, "created_at");
    
    if (content && created_at) {
        printf("Latest note: %s (created at %ld)\n", 
               cJSON_GetStringValue(content),
               (long)cJSON_GetNumberValue(created_at));
    }
    
    cJSON_Delete(event);
    return 0;
}

Publish Event

Function: nostr_relay_pool_publish()

int nostr_relay_pool_publish(
    nostr_relay_pool_t* pool,
    const char** relay_urls,
    int relay_count,
    cJSON* event);

Example:

int publish_text_note(nostr_relay_pool_t* pool, const char* content) {
    // Create a basic text note event (this is simplified - real implementation
    // would need proper signing with private key)
    cJSON* event = cJSON_CreateObject();
    cJSON_AddItemToObject(event, "kind", cJSON_CreateNumber(1));
    cJSON_AddItemToObject(event, "content", cJSON_CreateString(content));
    cJSON_AddItemToObject(event, "created_at", cJSON_CreateNumber(time(NULL)));
    
    // In real usage, you'd add pubkey, id, sig fields here
    cJSON_AddItemToObject(event, "pubkey", cJSON_CreateString("your_pubkey_hex"));
    cJSON_AddItemToObject(event, "id", cJSON_CreateString("event_id_hash"));
    cJSON_AddItemToObject(event, "sig", cJSON_CreateString("event_signature"));
    cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
    
    const char* relay_urls[] = {
        "wss://relay.damus.io",
        "wss://nos.lol",
        "wss://relay.nostr.band"
    };
    
    printf("Publishing note: %s\n", content);
    
    int success_count = nostr_relay_pool_publish(
        pool, relay_urls, 3, event);
    
    cJSON_Delete(event);
    
    printf("Successfully published to %d out of 3 relays\n", success_count);
    
    if (success_count == 0) {
        fprintf(stderr, "Failed to publish to any relay\n");
        return -1;
    }
    
    return success_count;
}

Status and Statistics

Get Relay Status

Function: nostr_relay_pool_get_relay_status()

nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
    nostr_relay_pool_t* pool, 
    const char* relay_url);

Example:

void check_relay_status(nostr_relay_pool_t* pool, const char* relay_url) {
    nostr_pool_relay_status_t status = nostr_relay_pool_get_relay_status(pool, relay_url);
    
    const char* status_str;
    switch (status) {
        case NOSTR_POOL_RELAY_DISCONNECTED:
            status_str = "DISCONNECTED";
            break;
        case NOSTR_POOL_RELAY_CONNECTING:
            status_str = "CONNECTING";
            break;
        case NOSTR_POOL_RELAY_CONNECTED:
            status_str = "CONNECTED";
            break;
        case NOSTR_POOL_RELAY_ERROR:
            status_str = "ERROR";
            break;
        default:
            status_str = "UNKNOWN";
            break;
    }
    
    printf("Relay %s status: %s\n", relay_url, status_str);
}

List All Relays

Function: nostr_relay_pool_list_relays()

int nostr_relay_pool_list_relays(
    nostr_relay_pool_t* pool,
    char*** relay_urls,
    nostr_pool_relay_status_t** statuses);

Example:

void print_all_relays(nostr_relay_pool_t* pool) {
    char** relay_urls = NULL;
    nostr_pool_relay_status_t* statuses = NULL;
    
    int count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
    
    if (count < 0) {
        printf("Failed to list relays\n");
        return;
    }
    
    if (count == 0) {
        printf("No relays configured\n");
        return;
    }
    
    printf("Configured relays (%d):\n", count);
    for (int i = 0; i < count; i++) {
        const char* status_str = (statuses[i] == NOSTR_POOL_RELAY_CONNECTED) ? 
                                 "CONNECTED" : "DISCONNECTED";
        printf("  %s - %s\n", relay_urls[i], status_str);
        
        // Free the duplicated URL string
        free(relay_urls[i]);
    }
    
    // Free the arrays
    free(relay_urls);
    free(statuses);
}

Get Relay Statistics

Function: nostr_relay_pool_get_relay_stats()

const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
    nostr_relay_pool_t* pool, 
    const char* relay_url);

Example:

void print_relay_stats(nostr_relay_pool_t* pool, const char* relay_url) {
    const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_url);
    
    if (!stats) {
        printf("No stats available for relay %s\n", relay_url);
        return;
    }
    
    printf("Statistics for %s:\n", relay_url);
    printf("  Connection attempts: %d\n", stats->connection_attempts);
    printf("  Connection failures: %d\n", stats->connection_failures);
    printf("  Events received: %d\n", stats->events_received);
    printf("  Events published: %d\n", stats->events_published);
    printf("  Events published OK: %d\n", stats->events_published_ok);
    printf("  Events published failed: %d\n", stats->events_published_failed);
    printf("  Query latency avg: %.2f ms\n", stats->query_latency_avg);
    printf("  Query samples: %d\n", stats->query_samples);
    printf("  Publish latency avg: %.2f ms\n", stats->publish_latency_avg);
    printf("  Publish samples: %d\n", stats->publish_samples);
    
    if (stats->last_event_time > 0) {
        printf("  Last event: %ld seconds ago\n", 
               time(NULL) - stats->last_event_time);
    }
}

Reset Relay Statistics

Function: nostr_relay_pool_reset_relay_stats()

int nostr_relay_pool_reset_relay_stats(
    nostr_relay_pool_t* pool, 
    const char* relay_url);

Example:

void reset_stats_for_relay(nostr_relay_pool_t* pool, const char* relay_url) {
    int result = nostr_relay_pool_reset_relay_stats(pool, relay_url);
    
    if (result == NOSTR_SUCCESS) {
        printf("Successfully reset statistics for %s\n", relay_url);
    } else {
        printf("Failed to reset statistics for %s\n", relay_url);
    }
}

Get Query Latency

Function: nostr_relay_pool_get_relay_query_latency()

double nostr_relay_pool_get_relay_query_latency(
    nostr_relay_pool_t* pool, 
    const char* relay_url);

Example:

void check_relay_performance(nostr_relay_pool_t* pool) {
    const char* relays[] = {
        "wss://relay.damus.io",
        "wss://nos.lol",
        "wss://relay.nostr.band"
    };
    
    printf("Relay performance comparison:\n");
    for (int i = 0; i < 3; i++) {
        double latency = nostr_relay_pool_get_relay_query_latency(pool, relays[i]);
        
        if (latency >= 0) {
            printf("  %s: %.2f ms average query latency\n", relays[i], latency);
        } else {
            printf("  %s: No latency data available\n", relays[i]);
        }
    }
}

Complete Example Application

#include "nostr_core.h"
#include "cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// Global context for the example
typedef struct {
    int event_count;
    int max_events;
} app_context_t;

void on_text_note(cJSON* event, const char* relay_url, void* user_data) {
    app_context_t* ctx = (app_context_t*)user_data;
    
    cJSON* content = cJSON_GetObjectItem(event, "content");
    if (content && cJSON_IsString(content)) {
        printf("[%s] Note #%d: %s\n", 
               relay_url, ++ctx->event_count, cJSON_GetStringValue(content));
    }
}

void on_subscription_complete(void* user_data) {
    printf("All relays finished sending stored events\n");
}

int main() {
    // Initialize pool
    nostr_relay_pool_t* pool = nostr_relay_pool_create();
    if (!pool) {
        fprintf(stderr, "Failed to create relay pool\n");
        return 1;
    }
    
    // Add relays
    const char* relays[] = {
        "wss://relay.damus.io",
        "wss://nos.lol"
    };
    
    for (int i = 0; i < 2; i++) {
        if (nostr_relay_pool_add_relay(pool, relays[i]) != NOSTR_SUCCESS) {
            fprintf(stderr, "Failed to add relay: %s\n", relays[i]);
        }
    }
    
    // Create filter for recent 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));
    
    // Set up context
    app_context_t ctx = {0, 10};
    
    // Subscribe
    nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
        pool, relays, 2, filter, on_text_note, on_subscription_complete, &ctx);
    
    cJSON_Delete(filter);
    
    if (!sub) {
        fprintf(stderr, "Failed to create subscription\n");
        nostr_relay_pool_destroy(pool);
        return 1;
    }
    
    // Run event loop for 30 seconds
    printf("Listening for events...\n");
    nostr_relay_pool_run(pool, 30000);
    
    // Print final stats
    for (int i = 0; i < 2; i++) {
        print_relay_stats(pool, relays[i]);
    }
    
    // Cleanup
    nostr_pool_subscription_close(sub);
    nostr_relay_pool_destroy(pool);
    
    printf("Application finished. Received %d events total.\n", ctx.event_count);
    return 0;
}

Notes

  • All functions are not thread-safe. Use from a single thread or add external synchronization.
  • Memory ownership: The pool duplicates filters and URLs internally. Caller owns returned events and must free them.
  • Event deduplication is applied pool-wide using a circular buffer of 1000 event IDs.
  • Ping functionality is currently disabled in this build.
  • Reconnection happens on-demand when sending, but active subscriptions are not automatically re-sent after reconnect.
  • Polling model: You must drive the event loop via nostr_relay_pool_run() or nostr_relay_pool_poll() to receive events.