Files
c-relay/src/config.c

904 lines
31 KiB
C

#include "config.h"
#include "default_config_event.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
// External database connection (from main.c)
extern sqlite3* g_db;
// Global configuration manager instance
config_manager_t g_config_manager = {0};
char g_database_path[512] = {0};
// Logging functions (defined in main.c)
extern void log_info(const char* message);
extern void log_success(const char* message);
extern void log_warning(const char* message);
extern void log_error(const char* message);
// Current configuration cache
static cJSON* g_current_config = NULL;
// Cache for initial configuration event (before database is initialized)
static cJSON* g_pending_config_event = NULL;
// ================================
// UTILITY FUNCTIONS
// ================================
char** find_existing_nrdb_files(void) {
DIR *dir;
struct dirent *entry;
char **files = NULL;
int count = 0;
dir = opendir(".");
if (dir == NULL) {
return NULL;
}
// Count .nrdb files
while ((entry = readdir(dir)) != NULL) {
if (strstr(entry->d_name, ".nrdb") != NULL) {
count++;
}
}
rewinddir(dir);
if (count == 0) {
closedir(dir);
return NULL;
}
// Allocate array for filenames
files = malloc((count + 1) * sizeof(char*));
if (!files) {
closedir(dir);
return NULL;
}
// Store filenames
int i = 0;
while ((entry = readdir(dir)) != NULL && i < count) {
if (strstr(entry->d_name, ".nrdb") != NULL) {
files[i] = malloc(strlen(entry->d_name) + 1);
if (files[i]) {
strcpy(files[i], entry->d_name);
i++;
}
}
}
files[i] = NULL; // Null terminate
closedir(dir);
return files;
}
char* extract_pubkey_from_filename(const char* filename) {
if (!filename) return NULL;
// Find .nrdb extension
const char* dot = strstr(filename, ".nrdb");
if (!dot) return NULL;
// Calculate pubkey length
size_t pubkey_len = dot - filename;
if (pubkey_len != 64) return NULL; // Invalid pubkey length
// Allocate and copy pubkey
char* pubkey = malloc(pubkey_len + 1);
if (!pubkey) return NULL;
strncpy(pubkey, filename, pubkey_len);
pubkey[pubkey_len] = '\0';
return pubkey;
}
char* get_database_name_from_relay_pubkey(const char* relay_pubkey) {
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
return NULL;
}
char* db_name = malloc(strlen(relay_pubkey) + 6); // +6 for ".nrdb\0"
if (!db_name) return NULL;
sprintf(db_name, "%s.nrdb", relay_pubkey);
return db_name;
}
// ================================
// DATABASE FUNCTIONS
// ================================
int create_database_with_relay_pubkey(const char* relay_pubkey) {
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
if (!db_name) {
log_error("Failed to generate database name");
return -1;
}
strncpy(g_database_path, db_name, sizeof(g_database_path) - 1);
g_database_path[sizeof(g_database_path) - 1] = '\0';
log_info("Creating database with relay pubkey");
printf(" Database: %s\n", db_name);
free(db_name);
return 0;
}
// ================================
// CONFIGURATION EVENT FUNCTIONS
// ================================
int store_config_event_in_database(const cJSON* event) {
if (!event || !g_db) {
return -1;
}
// Get event fields
cJSON* id_obj = cJSON_GetObjectItem(event, "id");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
cJSON* sig_obj = cJSON_GetObjectItem(event, "sig");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (!id_obj || !pubkey_obj || !created_at_obj || !kind_obj || !content_obj || !sig_obj || !tags_obj) {
return -1;
}
// Convert tags to JSON string
char* tags_str = cJSON_Print(tags_obj);
if (!tags_str) {
return -1;
}
// Insert or replace the configuration event (kind 33334 is replaceable)
const char* sql = "INSERT OR REPLACE INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare configuration event insert");
free(tags_str);
return -1;
}
sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id_obj), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey_obj), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at_obj));
sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind_obj));
sqlite3_bind_text(stmt, 5, "addressable", -1, SQLITE_STATIC); // kind 33334 is addressable
sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content_obj), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig_obj), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 8, tags_str, -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(tags_str);
if (rc == SQLITE_DONE) {
log_success("Configuration event stored in database");
return 0;
} else {
log_error("Failed to store configuration event");
return -1;
}
}
cJSON* load_config_event_from_database(const char* relay_pubkey) {
if (!g_db || !relay_pubkey) {
return NULL;
}
const char* sql;
sqlite3_stmt* stmt;
int rc;
// If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event
if (strlen(g_config_manager.admin_pubkey) > 0) {
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare configuration event query");
return NULL;
}
sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC);
} else {
// During existing relay startup, we don't know the admin pubkey yet
// Look for any kind 33334 configuration event (should only be one per relay)
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1";
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare configuration event query");
return NULL;
}
}
cJSON* event = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
// Reconstruct the event JSON from database columns
event = cJSON_CreateObject();
if (event) {
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 1);
cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0));
cJSON_AddStringToObject(event, "pubkey", event_pubkey);
cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
// If we didn't have admin pubkey, store it now
if (strlen(g_config_manager.admin_pubkey) == 0) {
strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
}
// Parse tags JSON
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
if (tags_str) {
cJSON* tags = cJSON_Parse(tags_str);
if (tags) {
cJSON_AddItemToObject(event, "tags", tags);
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
}
}
sqlite3_finalize(stmt);
return event;
}
// ================================
// CONFIGURATION ACCESS FUNCTIONS
// ================================
const char* get_config_value(const char* key) {
static char buffer[CONFIG_VALUE_MAX_LENGTH];
if (!key || !g_current_config) {
return NULL;
}
// Look for key in current configuration tags
cJSON* tags = cJSON_GetObjectItem(g_current_config, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return NULL;
}
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_key && tag_value &&
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
return buffer;
}
}
}
}
return NULL;
}
int get_config_int(const char* key, int default_value) {
const char* str_value = get_config_value(key);
if (!str_value) {
return default_value;
}
char* endptr;
long val = strtol(str_value, &endptr, 10);
if (endptr == str_value || *endptr != '\0') {
return default_value;
}
return (int)val;
}
int get_config_bool(const char* key, int default_value) {
const char* str_value = get_config_value(key);
if (!str_value) {
return default_value;
}
if (strcasecmp(str_value, "true") == 0 ||
strcasecmp(str_value, "yes") == 0 ||
strcasecmp(str_value, "1") == 0) {
return 1;
} else if (strcasecmp(str_value, "false") == 0 ||
strcasecmp(str_value, "no") == 0 ||
strcasecmp(str_value, "0") == 0) {
return 0;
}
return default_value;
}
// ================================
// FIRST-TIME STARTUP FUNCTIONS
// ================================
int is_first_time_startup(void) {
char** existing_files = find_existing_nrdb_files();
if (existing_files) {
// Free the array
for (int i = 0; existing_files[i]; i++) {
free(existing_files[i]);
}
free(existing_files);
return 0; // Not first time
}
return 1; // First time
}
// ================================
// COMPATIBILITY FUNCTIONS
// ================================
int init_configuration_system(const char* config_dir_override, const char* config_file_override) {
// Suppress unused parameter warnings for compatibility function
(void)config_dir_override;
(void)config_file_override;
log_info("Initializing event-based configuration system...");
// Clear configuration manager state
memset(&g_config_manager, 0, sizeof(config_manager_t));
g_config_manager.db = g_db;
// For now, set empty paths for compatibility
g_config_manager.config_file_path[0] = '\0';
log_success("Event-based configuration system initialized");
return 0;
}
void cleanup_configuration_system(void) {
log_info("Cleaning up configuration system...");
if (g_current_config) {
cJSON_Delete(g_current_config);
g_current_config = NULL;
}
if (g_pending_config_event) {
cJSON_Delete(g_pending_config_event);
g_pending_config_event = NULL;
}
memset(&g_config_manager, 0, sizeof(config_manager_t));
log_success("Configuration system cleaned up");
}
int set_database_config(const char* key, const char* value, const char* changed_by) {
// Suppress unused parameter warnings for compatibility function
(void)key;
(void)value;
(void)changed_by;
// Temporary compatibility function - does nothing for now
// In the new system, configuration is only updated via events
log_warning("set_database_config called - not supported in event-based config");
return 0;
}
// ================================
// KEY GENERATION FUNCTIONS
// ================================
// Helper function to generate random private key
int generate_random_private_key_bytes(unsigned char* privkey_bytes) {
if (!privkey_bytes) {
return -1;
}
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
log_error("Failed to open /dev/urandom for key generation");
return -1;
}
if (fread(privkey_bytes, 1, 32, urandom) != 32) {
log_error("Failed to read random bytes for private key");
fclose(urandom);
return -1;
}
fclose(urandom);
// Verify the private key is valid using nostr_core_lib
if (nostr_ec_private_key_verify(privkey_bytes) != NOSTR_SUCCESS) {
log_error("Generated private key failed validation");
return -1;
}
return 0;
}
// ================================
// DEFAULT CONFIG EVENT CREATION
// ================================
cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
const char* relay_privkey_hex,
const char* relay_pubkey_hex) {
if (!admin_privkey_bytes || !relay_privkey_hex || !relay_pubkey_hex) {
log_error("Invalid parameters for creating default config event");
return NULL;
}
log_info("Creating default configuration event...");
// Create tags array with default configuration values
cJSON* tags = cJSON_CreateArray();
if (!tags) {
log_error("Failed to create tags array");
return NULL;
}
// Add d tag with relay pubkey
cJSON* d_tag = cJSON_CreateArray();
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey_hex));
cJSON_AddItemToArray(tags, d_tag);
// Add relay keys
cJSON* relay_pubkey_tag = cJSON_CreateArray();
cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString("relay_pubkey"));
cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString(relay_pubkey_hex));
cJSON_AddItemToArray(tags, relay_pubkey_tag);
cJSON* relay_privkey_tag = cJSON_CreateArray();
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString("relay_privkey"));
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString(relay_privkey_hex));
cJSON_AddItemToArray(tags, relay_privkey_tag);
// Add all default configuration values
for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) {
cJSON* tag = cJSON_CreateArray();
cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].key));
cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].value));
cJSON_AddItemToArray(tags, tag);
}
// Create and sign event using nostr_core_lib
cJSON* event = nostr_create_and_sign_event(
33334, // kind
"C Nostr Relay Configuration", // content
tags, // tags
admin_privkey_bytes, // private key bytes for signing
time(NULL) // created_at timestamp
);
cJSON_Delete(tags); // Clean up tags as they were duplicated in nostr_create_and_sign_event
if (!event) {
log_error("Failed to create and sign configuration event");
return NULL;
}
// Log success information
cJSON* id_obj = cJSON_GetObjectItem(event, "id");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (id_obj && pubkey_obj) {
log_success("Default configuration event created successfully");
printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj));
printf(" Admin Public Key: %s\n", cJSON_GetStringValue(pubkey_obj));
}
return event;
}
// ================================
// IMPLEMENTED FUNCTIONS
// ================================
int first_time_startup_sequence(void) {
log_info("Starting first-time startup sequence...");
// 1. Generate admin keypair using /dev/urandom + nostr_core_lib
unsigned char admin_privkey_bytes[32];
char admin_privkey[65], admin_pubkey[65];
if (generate_random_private_key_bytes(admin_privkey_bytes) != 0) {
log_error("Failed to generate admin private key");
return -1;
}
nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey);
unsigned char admin_pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) {
log_error("Failed to derive admin public key");
return -1;
}
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
// 2. Generate relay keypair using /dev/urandom + nostr_core_lib
unsigned char relay_privkey_bytes[32];
char relay_privkey[65], relay_pubkey[65];
if (generate_random_private_key_bytes(relay_privkey_bytes) != 0) {
log_error("Failed to generate relay private key");
return -1;
}
nostr_bytes_to_hex(relay_privkey_bytes, 32, relay_privkey);
unsigned char relay_pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(relay_privkey_bytes, relay_pubkey_bytes) != NOSTR_SUCCESS) {
log_error("Failed to derive relay public key");
return -1;
}
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
// 3. Store keys in global config manager
strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0';
// 4. Create database with relay pubkey name
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
log_error("Failed to create database with relay pubkey");
return -1;
}
// 5. Create initial configuration event using defaults
cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey);
if (!config_event) {
log_error("Failed to create default configuration event");
return -1;
}
// 6. Try to store configuration event in database, but cache it if database isn't ready
if (store_config_event_in_database(config_event) == 0) {
log_success("Initial configuration event stored successfully");
} else {
log_warning("Failed to store initial configuration event - will retry after database init");
// Cache the event for later storage
if (g_pending_config_event) {
cJSON_Delete(g_pending_config_event);
}
g_pending_config_event = cJSON_Duplicate(config_event, 1);
}
// 7. Cache the current config
if (g_current_config) {
cJSON_Delete(g_current_config);
}
g_current_config = cJSON_Duplicate(config_event, 1);
// 8. Clean up
cJSON_Delete(config_event);
// 9. Print admin private key for user to save
printf("\n");
printf("=================================================================\n");
printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n");
printf("=================================================================\n");
printf("Admin Private Key: %s\n", admin_privkey);
printf("Admin Public Key: %s\n", admin_pubkey);
printf("\nRelay Private Key: %s\n", relay_privkey);
printf("Relay Public Key: %s\n", relay_pubkey);
printf("\nDatabase: %s\n", g_database_path);
printf("\nThis admin private key is needed to update configuration!\n");
printf("Store it safely - it will not be displayed again.\n");
printf("=================================================================\n");
printf("\n");
log_success("First-time startup sequence completed");
return 0;
}
int startup_existing_relay(const char* relay_pubkey) {
if (!relay_pubkey) {
log_error("Invalid relay pubkey for existing relay startup");
return -1;
}
log_info("Starting existing relay...");
printf(" Relay pubkey: %s\n", relay_pubkey);
// Store relay pubkey in global config manager
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
// Set database path
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
if (!db_name) {
log_error("Failed to generate database name");
return -1;
}
strncpy(g_database_path, db_name, sizeof(g_database_path) - 1);
g_database_path[sizeof(g_database_path) - 1] = '\0';
free(db_name);
// Load configuration event from database (after database is initialized)
// This will be done in apply_configuration_from_database()
log_success("Existing relay startup prepared");
return 0;
}
int process_configuration_event(const cJSON* event) {
if (!event) {
log_error("Invalid configuration event");
return -1;
}
log_info("Processing configuration event...");
// Validate event structure
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
log_error("Invalid event kind for configuration");
return -1;
}
if (!pubkey_obj) {
log_error("Missing pubkey in configuration event");
return -1;
}
// Verify it's from the admin
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
if (strlen(g_config_manager.admin_pubkey) > 0) {
if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) {
log_error("Configuration event not from authorized admin");
return -1;
}
}
// Comprehensive event validation using nostr_core_lib
log_info("Validating configuration event structure and signature...");
// First validate the event structure (fields, format, etc.)
if (nostr_validate_event_structure((cJSON*)event) != NOSTR_SUCCESS) {
log_error("Configuration event has invalid structure");
return -1;
}
// Then validate the cryptographic signature
if (nostr_verify_event_signature((cJSON*)event) != NOSTR_SUCCESS) {
log_error("Configuration event has invalid signature");
return -1;
}
log_success("Configuration event structure and signature validated successfully");
// Store in database
if (store_config_event_in_database(event) != 0) {
log_error("Failed to store configuration event");
return -1;
}
// Apply configuration
if (apply_configuration_from_event(event) != 0) {
log_error("Failed to apply configuration from event");
return -1;
}
log_success("Configuration event processed successfully");
return 0;
}
// ================================
// RUNTIME CONFIGURATION HANDLERS
// ================================
// External functions and globals from main.c that need to be updated
extern void update_subscription_manager_config(void);
extern void init_pow_config(void);
extern void init_expiration_config(void);
extern void init_relay_info(void);
// Compare configuration values between old and new config
static const char* get_config_value_from_event(const cJSON* event, const char* key) {
if (!event || !key) return NULL;
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) return NULL;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_key && tag_value &&
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
return cJSON_GetStringValue(tag_value);
}
}
}
}
return NULL;
}
// Check if a configuration value has changed
static int config_value_changed(const cJSON* old_config, const cJSON* new_config, const char* key) {
const char* old_value = get_config_value_from_event(old_config, key);
const char* new_value = get_config_value_from_event(new_config, key);
// Both NULL - no change
if (!old_value && !new_value) return 0;
// One is NULL, other isn't - changed
if (!old_value || !new_value) return 1;
// Compare string values
return strcmp(old_value, new_value) != 0;
}
// Apply runtime configuration changes by calling appropriate handlers
int apply_runtime_config_handlers(const cJSON* old_config, const cJSON* new_config) {
if (!new_config) return 0;
int handlers_applied = 0;
log_info("Checking for runtime configuration changes...");
// Subscription Manager Configuration
if (config_value_changed(old_config, new_config, "max_subscriptions_per_client") ||
config_value_changed(old_config, new_config, "max_total_subscriptions")) {
log_info("Subscription limits changed - updating subscription manager");
update_subscription_manager_config();
handlers_applied++;
}
// NIP-13 Proof of Work Configuration
if (config_value_changed(old_config, new_config, "pow_min_difficulty") ||
config_value_changed(old_config, new_config, "pow_mode")) {
log_info("PoW configuration changed - reinitializing PoW system");
init_pow_config();
handlers_applied++;
}
// NIP-40 Expiration Configuration
if (config_value_changed(old_config, new_config, "nip40_expiration_enabled") ||
config_value_changed(old_config, new_config, "nip40_expiration_strict") ||
config_value_changed(old_config, new_config, "nip40_expiration_filter") ||
config_value_changed(old_config, new_config, "nip40_expiration_grace_period")) {
log_info("Expiration configuration changed - reinitializing expiration system");
init_expiration_config();
handlers_applied++;
}
// NIP-11 Relay Information
if (config_value_changed(old_config, new_config, "relay_description") ||
config_value_changed(old_config, new_config, "relay_contact") ||
config_value_changed(old_config, new_config, "relay_software") ||
config_value_changed(old_config, new_config, "relay_version") ||
config_value_changed(old_config, new_config, "max_message_length") ||
config_value_changed(old_config, new_config, "max_event_tags") ||
config_value_changed(old_config, new_config, "max_content_length")) {
log_info("Relay information changed - reinitializing relay info");
init_relay_info();
handlers_applied++;
}
// Log configuration changes for audit
if (handlers_applied > 0) {
char audit_msg[512];
snprintf(audit_msg, sizeof(audit_msg),
"Configuration updated via kind 33334 event - %d system components reinitialized",
handlers_applied);
log_success(audit_msg);
} else {
log_info("No runtime configuration changes detected");
}
return handlers_applied;
}
int apply_configuration_from_event(const cJSON* event) {
if (!event) {
log_error("Invalid event for configuration application");
return -1;
}
log_info("Applying configuration from event...");
// Store previous config for comparison
cJSON* old_config = g_current_config;
// Update cached configuration
g_current_config = cJSON_Duplicate(event, 1);
// Extract admin pubkey if not already set
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) {
strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj),
sizeof(g_config_manager.admin_pubkey) - 1);
}
// Apply runtime configuration changes
int handlers_applied = apply_runtime_config_handlers(old_config, g_current_config);
// Clean up old config
if (old_config) {
cJSON_Delete(old_config);
}
char success_msg[256];
snprintf(success_msg, sizeof(success_msg),
"Configuration applied from event (%d handlers executed)", handlers_applied);
log_success(success_msg);
return 0;
}
// ================================
// REAL-TIME EVENT HANDLER (called from main.c)
// ================================
// Handle kind 33334 configuration events received via WebSocket
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size) {
if (!event) {
snprintf(error_message, error_size, "invalid: null configuration event");
return -1;
}
log_info("Handling configuration event from WebSocket");
// Use existing process_configuration_event function
if (process_configuration_event(event) == 0) {
// Success
error_message[0] = '\0'; // Empty error message indicates success
return 0;
} else {
// Failed to process
snprintf(error_message, error_size, "error: failed to process configuration event");
return -1;
}
}
// ================================
// RETRY INITIAL CONFIG EVENT STORAGE
// ================================
int retry_store_initial_config_event(void) {
if (!g_pending_config_event) {
// No pending event to store
return 0;
}
log_info("Retrying storage of initial configuration event...");
// Try to store the cached configuration event
if (store_config_event_in_database(g_pending_config_event) == 0) {
log_success("Initial configuration event stored successfully on retry");
// Clean up the pending event
cJSON_Delete(g_pending_config_event);
g_pending_config_event = NULL;
return 0;
} else {
log_error("Failed to store initial configuration event on retry");
return -1;
}
}