nostr_core_lib/POOL_API.md

755 lines
22 KiB
Markdown

# Relay Pool API Reference
This document describes the public API for the Nostr Relay Pool implementation in [`core_relay_pool.c`](nostr_core/core_relay_pool.c).
## Function Summary
| Function | Description |
|----------|-------------|
| [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) | Create and initialize a new relay pool |
| [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) | Destroy pool and cleanup all resources |
| [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) | Add a relay URL to the pool |
| [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) | Remove a relay URL from the pool |
| [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) | Create async subscription with callbacks |
| [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) | Close subscription and free resources |
| [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) | Run event loop for specified timeout |
| [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) | Single iteration poll and dispatch |
| [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) | Synchronous query returning event array |
| [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) | Get single most recent event |
| [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) | Publish event and wait for OK responses |
| [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) | Get connection status for a relay |
| [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) | List all relays and their statuses |
| [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) | Get detailed statistics for a relay |
| [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) | Reset statistics for a relay |
| [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) | Get average query latency for a relay |
## Pool Lifecycle
### Create Pool
**Function:** [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219)
```c
nostr_relay_pool_t* nostr_relay_pool_create(void);
```
**Example:**
```c
#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()`](nostr_core/core_relay_pool.c:304)
```c
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
```
**Example:**
```c
// 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()`](nostr_core/core_relay_pool.c:229)
```c
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:273)
```c
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
```
**Example:**
```c
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_core/core_relay_pool.c:399)
```c
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:**
```c
#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()`](nostr_core/core_relay_pool.c:491)
```c
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
```
**Example:**
```c
// 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()`](nostr_core/core_relay_pool.c:1192)
```c
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:1232)
```c
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
```
**Example:**
```c
// 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()`](nostr_core/core_relay_pool.c:695)
```c
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:**
```c
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()`](nostr_core/core_relay_pool.c:825)
```c
cJSON* nostr_relay_pool_get_event(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
int timeout_ms);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:866)
```c
int nostr_relay_pool_publish(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* event);
```
**Example:**
```c
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_core/core_relay_pool.c:944)
```c
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
nostr_relay_pool_t* pool,
const char* relay_url);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:960)
```c
int nostr_relay_pool_list_relays(
nostr_relay_pool_t* pool,
char*** relay_urls,
nostr_pool_relay_status_t** statuses);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:992)
```c
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:1008)
```c
int nostr_relay_pool_reset_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url);
```
**Example:**
```c
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()`](nostr_core/core_relay_pool.c:1045)
```c
double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool,
const char* relay_url);
```
**Example:**
```c
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
```c
#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()`](nostr_core/core_relay_pool.c:1192) or [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) to receive events.