Files
didactyl/test_pool.c

268 lines
8.2 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "./nostr_core_lib/cjson/cJSON.h"
#include "./nostr_core_lib/nostr_core/nostr_core.h"
static const char* k_default_relays[] = {
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.laantungir.net"
};
typedef struct {
int eose_received;
int events_received;
int kind3_events;
int max_p_tags;
} test_ctx_t;
static volatile int g_ws_eose_seen = 0;
static long long now_ms(void) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
return 0;
}
return ((long long)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
}
static int count_tag(cJSON* tags, const char* key) {
if (!tags || !cJSON_IsArray(tags) || !key) {
return 0;
}
int n = cJSON_GetArraySize(tags);
int count = 0;
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 1) {
continue;
}
cJSON* k = cJSON_GetArrayItem(tag, 0);
if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
count++;
}
}
return count;
}
static void on_event(cJSON* event, const char* relay_url, void* user_data) {
test_ctx_t* ctx = (test_ctx_t*)user_data;
if (!ctx || !event || !cJSON_IsObject(event)) {
return;
}
ctx->events_received++;
cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
int p_tags = count_tag(tags, "p");
int r_tags = count_tag(tags, "r");
int tag_count = (tags && cJSON_IsArray(tags)) ? cJSON_GetArraySize(tags) : 0;
int content_len = (content && cJSON_IsString(content) && content->valuestring) ? (int)strlen(content->valuestring) : 0;
if (p_tags > ctx->max_p_tags) {
ctx->max_p_tags = p_tags;
}
if (kind_val == 3) {
ctx->kind3_events++;
}
printf("[EVENT] relay=%s kind=%d created_at=%lld tags=%d p_tags=%d r_tags=%d content_len=%d id=%.16s pubkey=%.16s\n",
relay_url ? relay_url : "<unknown>",
kind_val,
(created_at && cJSON_IsNumber(created_at)) ? (long long)created_at->valuedouble : -1LL,
tag_count,
p_tags,
r_tags,
content_len,
(id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : "",
(pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : "");
}
static void on_eose(cJSON** events, int event_count, void* user_data) {
(void)events;
test_ctx_t* ctx = (test_ctx_t*)user_data;
if (!ctx) {
return;
}
ctx->eose_received = 1;
printf("[EOSE] received for subscription (event_count=%d)\n", event_count);
}
static void log_callback(int level, const char* component, const char* message, void* user_data) {
(void)user_data;
const char* lvl = "?";
switch (level) {
case NOSTR_LOG_LEVEL_ERROR: lvl = "ERROR"; break;
case NOSTR_LOG_LEVEL_WARN: lvl = "WARN"; break;
case NOSTR_LOG_LEVEL_INFO: lvl = "INFO"; break;
case NOSTR_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break;
case NOSTR_LOG_LEVEL_TRACE: lvl = "TRACE"; break;
default: break;
}
if (!component) component = "core";
if (!message) message = "";
if (strcmp(component, "websocket") == 0) {
printf("[NOSTR %s] [%s] %s\n", lvl, component, message);
if (strstr(message, "[\"EOSE\"") != NULL) {
g_ws_eose_seen = 1;
}
}
}
int main(int argc, char** argv) {
const char* npub = "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks";
int timeout_ms = 15000;
if (argc >= 2 && argv[1] && argv[1][0] != '\0') {
npub = argv[1];
}
if (argc >= 3) {
timeout_ms = atoi(argv[2]);
if (timeout_ms <= 0) {
timeout_ms = 15000;
}
}
unsigned char pubkey[32] = {0};
char pubkey_hex[65] = {0};
if (nostr_decode_npub(npub, pubkey) != 0) {
fprintf(stderr, "Failed to decode npub: %s\n", npub);
return 1;
}
nostr_bytes_to_hex(pubkey, 32, pubkey_hex);
if (nostr_crypto_init() != 0) {
fprintf(stderr, "nostr_crypto_init failed\n");
return 1;
}
nostr_set_log_callback(log_callback, NULL);
nostr_set_log_level(NOSTR_LOG_LEVEL_TRACE);
nostr_pool_reconnect_config_t* reconnect = nostr_pool_reconnect_config_default();
if (!reconnect) {
fprintf(stderr, "nostr_pool_reconnect_config_default failed\n");
nostr_crypto_cleanup();
return 1;
}
reconnect->enable_auto_reconnect = 1;
reconnect->ping_interval_seconds = 20;
reconnect->pong_timeout_seconds = 10;
nostr_relay_pool_t* pool = nostr_relay_pool_create(reconnect);
if (!pool) {
fprintf(stderr, "nostr_relay_pool_create failed\n");
nostr_crypto_cleanup();
return 1;
}
const int relay_count = (int)(sizeof(k_default_relays) / sizeof(k_default_relays[0]));
for (int i = 0; i < relay_count; i++) {
if (nostr_relay_pool_add_relay(pool, k_default_relays[i]) != 0) {
fprintf(stderr, "Failed to add relay: %s\n", k_default_relays[i]);
}
}
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
fprintf(stderr, "Failed to build filter JSON\n");
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
nostr_relay_pool_destroy(pool);
nostr_crypto_cleanup();
return 1;
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(3));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 16);
test_ctx_t ctx;
memset(&ctx, 0, sizeof(ctx));
printf("Target npub: %s\n", npub);
printf("Target hex : %s\n", pubkey_hex);
printf("Relays (%d):\n", relay_count);
for (int i = 0; i < relay_count; i++) {
printf(" - %s\n", k_default_relays[i]);
}
printf("Subscribing to kind=3 with timeout=%d ms\n", timeout_ms);
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
pool,
k_default_relays,
relay_count,
filter,
on_event,
on_eose,
&ctx,
0,
0,
NOSTR_POOL_EOSE_FIRST,
12,
20
);
if (!sub) {
fprintf(stderr, "nostr_relay_pool_subscribe failed\n");
cJSON_Delete(filter);
nostr_relay_pool_destroy(pool);
nostr_crypto_cleanup();
return 1;
}
long long started = now_ms();
while (!ctx.eose_received && !g_ws_eose_seen) {
long long elapsed = now_ms() - started;
if (elapsed >= timeout_ms) {
break;
}
(void)nostr_relay_pool_poll(pool, 100);
}
printf("\nSummary:\n");
printf(" EOSE callback : %s\n", ctx.eose_received ? "yes" : "no");
printf(" Websocket EOSE: %s\n", g_ws_eose_seen ? "yes" : "no");
printf(" Events seen : %d\n", ctx.events_received);
printf(" Kind-3 events : %d\n", ctx.kind3_events);
printf(" Max p-tags : %d\n", ctx.max_p_tags);
if (!ctx.eose_received && g_ws_eose_seen) {
fprintf(stderr, "NOTE: Relay sent EOSE on websocket but subscription callback did not fire (pool EOSE behavior mismatch).\n");
}
if (!ctx.eose_received && !g_ws_eose_seen) {
fprintf(stderr, "Timed out waiting for EOSE after %d ms\n", timeout_ms);
}
(void)nostr_pool_subscription_close(sub);
cJSON_Delete(filter);
nostr_relay_pool_destroy(pool);
nostr_crypto_cleanup();
return (ctx.eose_received || g_ws_eose_seen) ? 0 : 2;
}