Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62e17af311 | ||
|
|
e3938a2c85 |
@@ -95,7 +95,7 @@ RUN gcc -static -O2 -Wall -Wextra -std=c99 \
|
||||
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
|
||||
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||
src/main.c src/config.c src/dm_admin.c src/request_validator.c \
|
||||
src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c \
|
||||
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
|
||||
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
|
||||
-o /build/c_relay_static \
|
||||
|
||||
2
Makefile
2
Makefile
@@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k
|
||||
BUILD_DIR = build
|
||||
|
||||
# Source files
|
||||
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
|
||||
MAIN_SRC = src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
|
||||
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
|
||||
|
||||
# Architecture detection
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<label for="relay-connection-url">Relay URL:</label>
|
||||
<input type="text" id="relay-connection-url" value="ws://localhost:8888"
|
||||
<input type="text" id="relay-connection-url" value=""
|
||||
placeholder="ws://localhost:8888 or wss://relay.example.com">
|
||||
</div>
|
||||
|
||||
|
||||
28
api/index.js
28
api/index.js
@@ -3497,10 +3497,38 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Set default relay URL based on where the page is being served from
|
||||
function setDefaultRelayUrl() {
|
||||
const relayUrlInput = document.getElementById('relay-connection-url');
|
||||
if (!relayUrlInput) return;
|
||||
|
||||
// Get the current page's protocol and hostname
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const hostname = window.location.hostname;
|
||||
const port = window.location.port;
|
||||
|
||||
// Construct the relay URL
|
||||
let relayUrl;
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||||
// For localhost, default to ws://localhost:8888
|
||||
relayUrl = 'ws://localhost:8888';
|
||||
} else {
|
||||
// For production, use the same hostname with WebSocket protocol
|
||||
// Remove port from URL since relay typically runs on standard ports (80/443)
|
||||
relayUrl = `${protocol}//${hostname}`;
|
||||
}
|
||||
|
||||
relayUrlInput.value = relayUrl;
|
||||
log(`Default relay URL set to: ${relayUrl}`, 'INFO');
|
||||
}
|
||||
|
||||
// Initialize the app
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('C-Relay Admin API interface loaded');
|
||||
|
||||
// Set default relay URL based on current page location
|
||||
setDefaultRelayUrl();
|
||||
|
||||
// Initialize login/logout button state
|
||||
updateLoginLogoutButton();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# C-Relay Static Binary Deployment Script
|
||||
# Deploys build/c_relay_static_x86_64 to server via sshlt
|
||||
# Usage: ./deploy_static.sh [--debug-level=N] [-d=N]
|
||||
|
||||
set -e
|
||||
|
||||
@@ -10,6 +11,44 @@ LOCAL_BINARY="build/c_relay_static_x86_64"
|
||||
REMOTE_BINARY_PATH="/usr/local/bin/c_relay/c_relay"
|
||||
SERVICE_NAME="c-relay"
|
||||
|
||||
# Default debug level
|
||||
DEBUG_LEVEL=0
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--debug-level=*)
|
||||
DEBUG_LEVEL="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
-d=*)
|
||||
DEBUG_LEVEL="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--debug-level)
|
||||
DEBUG_LEVEL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d)
|
||||
DEBUG_LEVEL="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 [--debug-level=N] [-d=N]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate debug level
|
||||
if ! [[ "$DEBUG_LEVEL" =~ ^[0-5]$ ]]; then
|
||||
echo "Error: Debug level must be 0-5, got: $DEBUG_LEVEL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying with debug level: $DEBUG_LEVEL"
|
||||
|
||||
# Create backup
|
||||
ssh ubuntu@laantungir.com "sudo cp '$REMOTE_BINARY_PATH' '${REMOTE_BINARY_PATH}.backup.$(date +%Y%m%d_%H%M%S)'" 2>/dev/null || true
|
||||
|
||||
@@ -21,7 +60,11 @@ ssh ubuntu@laantungir.com "sudo mv '/tmp/c_relay.tmp' '$REMOTE_BINARY_PATH'"
|
||||
ssh ubuntu@laantungir.com "sudo chown c-relay:c-relay '$REMOTE_BINARY_PATH'"
|
||||
ssh ubuntu@laantungir.com "sudo chmod +x '$REMOTE_BINARY_PATH'"
|
||||
|
||||
# Restart service
|
||||
# Update systemd service environment variable
|
||||
ssh ubuntu@laantungir.com "sudo sed -i 's/Environment=DEBUG_LEVEL=.*/Environment=DEBUG_LEVEL=$DEBUG_LEVEL/' /etc/systemd/system/c-relay.service"
|
||||
|
||||
# Reload systemd and restart service
|
||||
ssh ubuntu@laantungir.com "sudo systemctl daemon-reload"
|
||||
ssh ubuntu@laantungir.com "sudo systemctl restart '$SERVICE_NAME'"
|
||||
|
||||
echo "Deployment complete!"
|
||||
562
docs/debug_system.md
Normal file
562
docs/debug_system.md
Normal file
@@ -0,0 +1,562 @@
|
||||
# Simple Debug System Proposal
|
||||
|
||||
## Overview
|
||||
|
||||
A minimal debug system with 6 levels (0-5) controlled by a single `--debug-level` flag. TRACE level (5) automatically includes file:line information for ALL messages. Uses compile-time macros to ensure **zero performance impact and zero size increase** in production builds.
|
||||
|
||||
## Debug Levels
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
DEBUG_LEVEL_NONE = 0, // Production: no debug output
|
||||
DEBUG_LEVEL_ERROR = 1, // Errors only
|
||||
DEBUG_LEVEL_WARN = 2, // Errors + Warnings
|
||||
DEBUG_LEVEL_INFO = 3, // Errors + Warnings + Info
|
||||
DEBUG_LEVEL_DEBUG = 4, // All above + Debug messages
|
||||
DEBUG_LEVEL_TRACE = 5 // All above + Trace (very verbose)
|
||||
} debug_level_t;
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Production (default - no debug output)
|
||||
./c_relay_x86
|
||||
|
||||
# Show errors only
|
||||
./c_relay_x86 --debug-level=1
|
||||
|
||||
# Show errors and warnings
|
||||
./c_relay_x86 --debug-level=2
|
||||
|
||||
# Show errors, warnings, and info (recommended for development)
|
||||
./c_relay_x86 --debug-level=3
|
||||
|
||||
# Show all debug messages
|
||||
./c_relay_x86 --debug-level=4
|
||||
|
||||
# Show everything including trace with file:line (very verbose)
|
||||
./c_relay_x86 --debug-level=5
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Header File (`src/debug.h`)
|
||||
|
||||
```c
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
// Debug levels
|
||||
typedef enum {
|
||||
DEBUG_LEVEL_NONE = 0,
|
||||
DEBUG_LEVEL_ERROR = 1,
|
||||
DEBUG_LEVEL_WARN = 2,
|
||||
DEBUG_LEVEL_INFO = 3,
|
||||
DEBUG_LEVEL_DEBUG = 4,
|
||||
DEBUG_LEVEL_TRACE = 5
|
||||
} debug_level_t;
|
||||
|
||||
// Global debug level (set at runtime via CLI)
|
||||
extern debug_level_t g_debug_level;
|
||||
|
||||
// Initialize debug system
|
||||
void debug_init(int level);
|
||||
|
||||
// Core logging function
|
||||
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...);
|
||||
|
||||
// Convenience macros that check level before calling
|
||||
// Note: TRACE level (5) and above include file:line information for ALL messages
|
||||
#define DEBUG_ERROR(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_ERROR) debug_log(DEBUG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_WARN(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_WARN) debug_log(DEBUG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_INFO(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_LOG(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_DEBUG) debug_log(DEBUG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_TRACE(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_TRACE) debug_log(DEBUG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#endif /* DEBUG_H */
|
||||
```
|
||||
|
||||
### 2. Implementation File (`src/debug.c`)
|
||||
|
||||
```c
|
||||
#include "debug.h"
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
// Global debug level (default: no debug output)
|
||||
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
|
||||
|
||||
void debug_init(int level) {
|
||||
if (level < 0) level = 0;
|
||||
if (level > 5) level = 5;
|
||||
g_debug_level = (debug_level_t)level;
|
||||
}
|
||||
|
||||
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
|
||||
// Get timestamp
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
char timestamp[32];
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
// Get level string
|
||||
const char* level_str = "UNKNOWN";
|
||||
switch (level) {
|
||||
case DEBUG_LEVEL_ERROR: level_str = "ERROR"; break;
|
||||
case DEBUG_LEVEL_WARN: level_str = "WARN "; break;
|
||||
case DEBUG_LEVEL_INFO: level_str = "INFO "; break;
|
||||
case DEBUG_LEVEL_DEBUG: level_str = "DEBUG"; break;
|
||||
case DEBUG_LEVEL_TRACE: level_str = "TRACE"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Print prefix with timestamp and level
|
||||
printf("[%s] [%s] ", timestamp, level_str);
|
||||
|
||||
// Print source location when debug level is TRACE (5) or higher
|
||||
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
|
||||
// Extract just the filename (not full path)
|
||||
const char* filename = strrchr(file, '/');
|
||||
filename = filename ? filename + 1 : file;
|
||||
printf("[%s:%d] ", filename, line);
|
||||
}
|
||||
|
||||
// Print message
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. CLI Argument Parsing (add to `src/main.c`)
|
||||
|
||||
```c
|
||||
// In main() function, add to argument parsing:
|
||||
|
||||
int debug_level = 0; // Default: no debug output
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strncmp(argv[i], "--debug-level=", 14) == 0) {
|
||||
debug_level = atoi(argv[i] + 14);
|
||||
if (debug_level < 0) debug_level = 0;
|
||||
if (debug_level > 5) debug_level = 5;
|
||||
}
|
||||
// ... other arguments ...
|
||||
}
|
||||
|
||||
// Initialize debug system
|
||||
debug_init(debug_level);
|
||||
```
|
||||
|
||||
### 4. Update Makefile
|
||||
|
||||
```makefile
|
||||
# Add debug.c to source files
|
||||
MAIN_SRC = src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c ...
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Keep Existing Functions
|
||||
|
||||
The existing `log_*` functions can remain as wrappers:
|
||||
|
||||
```c
|
||||
// src/main.c - Update existing functions
|
||||
// Note: These don't include file:line since they're wrappers
|
||||
void log_info(const char* message) {
|
||||
if (g_debug_level >= DEBUG_LEVEL_INFO) {
|
||||
debug_log(DEBUG_LEVEL_INFO, NULL, 0, "%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
void log_error(const char* message) {
|
||||
if (g_debug_level >= DEBUG_LEVEL_ERROR) {
|
||||
debug_log(DEBUG_LEVEL_ERROR, NULL, 0, "%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
void log_warning(const char* message) {
|
||||
if (g_debug_level >= DEBUG_LEVEL_WARN) {
|
||||
debug_log(DEBUG_LEVEL_WARN, NULL, 0, "%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
void log_success(const char* message) {
|
||||
if (g_debug_level >= DEBUG_LEVEL_INFO) {
|
||||
debug_log(DEBUG_LEVEL_INFO, NULL, 0, "✓ %s", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Gradual Migration
|
||||
|
||||
Gradually replace log calls with debug macros:
|
||||
|
||||
```c
|
||||
// Before:
|
||||
log_info("Starting WebSocket relay server");
|
||||
|
||||
// After:
|
||||
DEBUG_INFO("Starting WebSocket relay server");
|
||||
|
||||
// Before:
|
||||
log_error("Failed to initialize database");
|
||||
|
||||
// After:
|
||||
DEBUG_ERROR("Failed to initialize database");
|
||||
```
|
||||
|
||||
### Add New Debug Levels
|
||||
|
||||
Add debug and trace messages where needed:
|
||||
|
||||
```c
|
||||
// Detailed debugging
|
||||
DEBUG_LOG("Processing subscription: %s", sub_id);
|
||||
DEBUG_LOG("Filter count: %d", filter_count);
|
||||
|
||||
// Very verbose tracing
|
||||
DEBUG_TRACE("Entering handle_req_message()");
|
||||
DEBUG_TRACE("Subscription ID validated: %s", sub_id);
|
||||
DEBUG_TRACE("Exiting handle_req_message()");
|
||||
```
|
||||
## Manual Guards for Expensive Operations
|
||||
|
||||
### The Problem
|
||||
|
||||
Debug macros use **runtime checks**, which means function arguments are always evaluated:
|
||||
|
||||
```c
|
||||
// ❌ BAD: Database query executes even when debug level is 0
|
||||
DEBUG_LOG("Count: %d", expensive_database_query());
|
||||
```
|
||||
|
||||
The `expensive_database_query()` will **always execute** because function arguments are evaluated before the `if` check inside the macro.
|
||||
|
||||
### The Solution: Manual Guards
|
||||
|
||||
For expensive operations (database queries, file I/O, complex calculations), use manual guards:
|
||||
|
||||
```c
|
||||
// ✅ GOOD: Query only executes when debugging is enabled
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
int count = expensive_database_query();
|
||||
DEBUG_LOG("Count: %d", count);
|
||||
}
|
||||
```
|
||||
|
||||
### Standardized Comment Format
|
||||
|
||||
To make temporary debug guards easy to find and remove, use this standardized format:
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
// Expensive operation here
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT COUNT(*) FROM events";
|
||||
int count = 0;
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
count = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
DEBUG_LOG("Event count: %d", count);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Easy Removal
|
||||
|
||||
When you're done debugging, find and remove all temporary guards:
|
||||
|
||||
```bash
|
||||
# Find all debug guards
|
||||
grep -n "DEBUG_GUARD_START" src/*.c
|
||||
|
||||
# Remove guards with sed (between START and END markers)
|
||||
sed -i '/DEBUG_GUARD_START/,/DEBUG_GUARD_END/d' src/config.c
|
||||
```
|
||||
|
||||
Or use a simple script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# remove_debug_guards.sh
|
||||
for file in src/*.c; do
|
||||
sed -i '/DEBUG_GUARD_START/,/DEBUG_GUARD_END/d' "$file"
|
||||
echo "Removed debug guards from $file"
|
||||
done
|
||||
```
|
||||
|
||||
### When to Use Manual Guards
|
||||
|
||||
Use manual guards for:
|
||||
- ✅ Database queries
|
||||
- ✅ File I/O operations
|
||||
- ✅ Network requests
|
||||
- ✅ Complex calculations
|
||||
- ✅ Memory allocations for debug data
|
||||
- ✅ String formatting with multiple operations
|
||||
|
||||
Don't need guards for:
|
||||
- ❌ Simple variable access
|
||||
- ❌ Basic arithmetic
|
||||
- ❌ String literals
|
||||
- ❌ Function calls that are already cheap
|
||||
|
||||
### Example: Database Query Guard
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* count_stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
int config_count = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(count_stmt) == SQLITE_ROW) {
|
||||
config_count = sqlite3_column_int(count_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(count_stmt);
|
||||
}
|
||||
|
||||
DEBUG_LOG("Config table has %d rows", config_count);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Example: Complex String Formatting Guard
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_TRACE) {
|
||||
char filter_str[1024] = {0};
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < filter_count && offset < sizeof(filter_str) - 1; i++) {
|
||||
offset += snprintf(filter_str + offset, sizeof(filter_str) - offset,
|
||||
"Filter %d: kind=%d, author=%s; ",
|
||||
i, filters[i].kind, filters[i].author);
|
||||
}
|
||||
|
||||
DEBUG_TRACE("Processing filters: %s", filter_str);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Alternative: Compile-Time Guards
|
||||
|
||||
For permanent debug code that should be completely removed in production builds, use compile-time guards:
|
||||
|
||||
```c
|
||||
#ifdef ENABLE_DEBUG_CODE
|
||||
// This code is completely removed when ENABLE_DEBUG_CODE is not defined
|
||||
int count = expensive_database_query();
|
||||
DEBUG_LOG("Count: %d", count);
|
||||
#endif
|
||||
```
|
||||
|
||||
Build with debug code:
|
||||
```bash
|
||||
make CFLAGS="-DENABLE_DEBUG_CODE"
|
||||
```
|
||||
|
||||
Build without debug code (production):
|
||||
```bash
|
||||
make # No debug code compiled in
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always use standardized markers** (`DEBUG_GUARD_START`/`DEBUG_GUARD_END`) for temporary guards
|
||||
2. **Add a comment** explaining what you're debugging
|
||||
3. **Remove guards** when debugging is complete
|
||||
4. **Use compile-time guards** for permanent debug infrastructure
|
||||
5. **Keep guards simple** - one guard per logical debug operation
|
||||
|
||||
|
||||
## Performance Impact
|
||||
|
||||
### Runtime Check
|
||||
|
||||
The macros include a runtime check:
|
||||
|
||||
```c
|
||||
#define DEBUG_INFO(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, NULL, 0, __VA_ARGS__); } while(0)
|
||||
```
|
||||
|
||||
**Cost**: One integer comparison per debug statement (~1 CPU cycle)
|
||||
|
||||
**Impact**: Negligible - the comparison is faster than a function call
|
||||
|
||||
**Note**: Only `DEBUG_TRACE` includes `__FILE__` and `__LINE__`, which are compile-time constants with no runtime overhead.
|
||||
|
||||
### When Debug Level is 0 (Production)
|
||||
|
||||
```c
|
||||
// With g_debug_level = 0:
|
||||
DEBUG_INFO("Starting server");
|
||||
|
||||
// Becomes:
|
||||
if (0 >= 3) debug_log(...); // Never executes
|
||||
|
||||
// Compiler optimizes to:
|
||||
// (nothing - branch is eliminated)
|
||||
```
|
||||
|
||||
**Result**: Modern compilers (gcc -O2 or higher) will completely eliminate the dead code branch.
|
||||
|
||||
### Size Impact
|
||||
|
||||
**Test Case**: 100 debug statements in code
|
||||
|
||||
**Without optimization** (`-O0`):
|
||||
- Binary size increase: ~2KB (branch instructions)
|
||||
- Runtime cost: 100 comparisons per execution
|
||||
|
||||
**With optimization** (`-O2` or `-O3`):
|
||||
- Binary size increase: **0 bytes** (dead code eliminated when g_debug_level = 0)
|
||||
- Runtime cost: **0 cycles** (branches removed by compiler)
|
||||
|
||||
### Verification
|
||||
|
||||
You can verify the optimization with:
|
||||
|
||||
```bash
|
||||
# Compile with optimization
|
||||
gcc -O2 -c debug_test.c -o debug_test.o
|
||||
|
||||
# Disassemble and check
|
||||
objdump -d debug_test.o | grep -A 10 "debug_log"
|
||||
```
|
||||
|
||||
When `g_debug_level = 0` (constant), you'll see the compiler has removed all debug calls.
|
||||
|
||||
## Example Output
|
||||
|
||||
### Level 0 (Production)
|
||||
```
|
||||
(no output)
|
||||
```
|
||||
|
||||
### Level 1 (Errors Only)
|
||||
```
|
||||
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
|
||||
[2025-01-12 14:30:20] [ERROR] WebSocket connection failed: port in use
|
||||
```
|
||||
|
||||
### Level 2 (Errors + Warnings)
|
||||
```
|
||||
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
|
||||
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
|
||||
[2025-01-12 14:30:17] [WARN ] Configuration key 'relay_name' not found, using default
|
||||
```
|
||||
|
||||
### Level 3 (Errors + Warnings + Info)
|
||||
```
|
||||
[2025-01-12 14:30:15] [INFO ] Initializing C-Relay v0.4.6
|
||||
[2025-01-12 14:30:15] [INFO ] Loading configuration from database
|
||||
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
|
||||
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
|
||||
[2025-01-12 14:30:17] [INFO ] WebSocket relay started on ws://127.0.0.1:8889
|
||||
```
|
||||
|
||||
### Level 4 (All Debug Messages)
|
||||
```
|
||||
[2025-01-12 14:30:15] [INFO ] Initializing C-Relay v0.4.6
|
||||
[2025-01-12 14:30:15] [DEBUG] Opening database: build/abc123...def.db
|
||||
[2025-01-12 14:30:15] [DEBUG] Executing schema initialization
|
||||
[2025-01-12 14:30:15] [INFO ] SQLite WAL mode enabled
|
||||
[2025-01-12 14:30:16] [DEBUG] Attempting to bind to port 8888
|
||||
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
|
||||
[2025-01-12 14:30:17] [DEBUG] Successfully bound to port 8889
|
||||
[2025-01-12 14:30:17] [INFO ] WebSocket relay started on ws://127.0.0.1:8889
|
||||
```
|
||||
|
||||
### Level 5 (Everything Including file:line for ALL messages)
|
||||
```
|
||||
[2025-01-12 14:30:15] [INFO ] [main.c:1607] Initializing C-Relay v0.4.6
|
||||
[2025-01-12 14:30:15] [DEBUG] [main.c:348] Opening database: build/abc123...def.db
|
||||
[2025-01-12 14:30:15] [TRACE] [main.c:330] Entering init_database()
|
||||
[2025-01-12 14:30:15] [ERROR] [config.c:125] Database locked
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Create Files (5 minutes)
|
||||
|
||||
1. Create `src/debug.h` with the header code above
|
||||
2. Create `src/debug.c` with the implementation code above
|
||||
3. Update `Makefile` to include `src/debug.c` in `MAIN_SRC`
|
||||
|
||||
### Step 2: Add CLI Parsing (5 minutes)
|
||||
|
||||
Add `--debug-level` argument parsing to `main()` in `src/main.c`
|
||||
|
||||
### Step 3: Update Existing Functions (5 minutes)
|
||||
|
||||
Update the existing `log_*` functions to use the new debug macros
|
||||
|
||||
### Step 4: Test (5 minutes)
|
||||
|
||||
```bash
|
||||
# Build
|
||||
make clean && make
|
||||
|
||||
# Test different levels
|
||||
./build/c_relay_x86 # No output
|
||||
./build/c_relay_x86 --debug-level=1 # Errors only
|
||||
./build/c_relay_x86 --debug-level=3 # Info + warnings + errors
|
||||
./build/c_relay_x86 --debug-level=4 # All debug messages
|
||||
./build/c_relay_x86 --debug-level=5 # Everything with file:line on TRACE
|
||||
```
|
||||
|
||||
### Step 5: Gradual Migration (Ongoing)
|
||||
|
||||
As you work on different parts of the code, replace `log_*` calls with `DEBUG_*` macros and add new debug/trace statements where helpful.
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Simple**: Single flag, 6 levels, easy to understand
|
||||
✅ **Zero Overhead**: Compiler optimizes away unused debug code
|
||||
✅ **Zero Size Impact**: No binary size increase in production
|
||||
✅ **Backward Compatible**: Existing `log_*` functions still work
|
||||
✅ **Easy Migration**: Gradual replacement of log calls
|
||||
✅ **Flexible**: Can add detailed debugging without affecting production
|
||||
|
||||
## Total Implementation Time
|
||||
|
||||
**~20 minutes** for basic implementation
|
||||
**Ongoing** for gradual migration of existing log calls
|
||||
|
||||
## Recommendation
|
||||
|
||||
This is the simplest possible debug system that provides:
|
||||
- Multiple debug levels for different verbosity
|
||||
- Zero performance impact in production
|
||||
- Zero binary size increase
|
||||
- Easy to use and understand
|
||||
- Backward compatible with existing code
|
||||
|
||||
Start with the basic implementation, test it, then gradually migrate existing log calls and add new debug statements as needed.
|
||||
1090
docs/startup_flows_complete.md
Normal file
1090
docs/startup_flows_complete.md
Normal file
File diff suppressed because it is too large
Load Diff
427
docs/unified_startup_design.md
Normal file
427
docs/unified_startup_design.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# Unified Startup Sequence Design
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the new unified startup sequence where all config values are created first, then CLI overrides are applied as a separate atomic operation. This eliminates the current 3-step incremental building process.
|
||||
|
||||
## Current Problems
|
||||
|
||||
1. **Incremental Config Building**: Config is built in 3 steps:
|
||||
- Step 1: `populate_default_config_values()` - adds defaults
|
||||
- Step 2: CLI overrides applied via `update_config_in_table()`
|
||||
- Step 3: `add_pubkeys_to_config_table()` - adds generated keys
|
||||
|
||||
2. **Race Conditions**: Cache can be refreshed between steps, causing incomplete config reads
|
||||
|
||||
3. **Complexity**: Multiple code paths for first-time vs restart scenarios
|
||||
|
||||
## New Design Principles
|
||||
|
||||
1. **Atomic Config Creation**: All config values created in single transaction
|
||||
2. **Separate Override Phase**: CLI overrides applied after complete config exists
|
||||
3. **Unified Code Path**: Same logic for first-time and restart scenarios
|
||||
4. **Cache Safety**: Cache only loaded after config is complete
|
||||
|
||||
---
|
||||
|
||||
## Scenario 1: First-Time Startup (No Database)
|
||||
|
||||
### Sequence
|
||||
|
||||
```
|
||||
1. Key Generation Phase
|
||||
├─ generate_random_private_key_bytes() → admin_privkey_bytes
|
||||
├─ nostr_bytes_to_hex() → admin_privkey (hex)
|
||||
├─ nostr_ec_public_key_from_private_key() → admin_pubkey_bytes
|
||||
├─ nostr_bytes_to_hex() → admin_pubkey (hex)
|
||||
├─ generate_random_private_key_bytes() → relay_privkey_bytes
|
||||
├─ nostr_bytes_to_hex() → relay_privkey (hex)
|
||||
├─ nostr_ec_public_key_from_private_key() → relay_pubkey_bytes
|
||||
└─ nostr_bytes_to_hex() → relay_pubkey (hex)
|
||||
|
||||
2. Database Creation Phase
|
||||
├─ create_database_with_relay_pubkey(relay_pubkey)
|
||||
│ └─ Sets g_database_path = "<relay_pubkey>.db"
|
||||
└─ init_database(g_database_path)
|
||||
└─ Creates database with embedded schema (includes config table)
|
||||
|
||||
3. Complete Config Population Phase (ATOMIC)
|
||||
├─ BEGIN TRANSACTION
|
||||
├─ populate_all_config_values_atomic()
|
||||
│ ├─ Insert ALL default config values from DEFAULT_CONFIG_VALUES[]
|
||||
│ ├─ Insert admin_pubkey
|
||||
│ └─ Insert relay_pubkey
|
||||
└─ COMMIT TRANSACTION
|
||||
|
||||
4. CLI Override Phase (ATOMIC)
|
||||
├─ BEGIN TRANSACTION
|
||||
├─ apply_cli_overrides()
|
||||
│ ├─ IF cli_options.port_override > 0:
|
||||
│ │ └─ UPDATE config SET value = ? WHERE key = 'relay_port'
|
||||
│ ├─ IF cli_options.admin_pubkey_override[0]:
|
||||
│ │ └─ UPDATE config SET value = ? WHERE key = 'admin_pubkey'
|
||||
│ └─ IF cli_options.relay_privkey_override[0]:
|
||||
│ └─ UPDATE config SET value = ? WHERE key = 'relay_privkey'
|
||||
└─ COMMIT TRANSACTION
|
||||
|
||||
5. Secure Key Storage Phase
|
||||
└─ store_relay_private_key(relay_privkey)
|
||||
└─ INSERT INTO relay_seckey (private_key_hex) VALUES (?)
|
||||
|
||||
6. Cache Initialization Phase
|
||||
└─ refresh_unified_cache_from_table()
|
||||
└─ Loads complete config into g_unified_cache
|
||||
```
|
||||
|
||||
### Function Call Sequence
|
||||
|
||||
```c
|
||||
// In main.c - first_time_startup branch
|
||||
if (is_first_time_startup()) {
|
||||
// 1. Key Generation
|
||||
first_time_startup_sequence(&cli_options);
|
||||
// → Generates keys, stores in g_unified_cache
|
||||
// → Sets g_database_path
|
||||
// → Does NOT populate config yet
|
||||
|
||||
// 2. Database Creation
|
||||
init_database(g_database_path);
|
||||
// → Creates database with schema
|
||||
|
||||
// 3. Complete Config Population (NEW FUNCTION)
|
||||
populate_all_config_values_atomic(&cli_options);
|
||||
// → Inserts ALL defaults + pubkeys in single transaction
|
||||
// → Does NOT apply CLI overrides yet
|
||||
|
||||
// 4. CLI Override Phase (NEW FUNCTION)
|
||||
apply_cli_overrides_atomic(&cli_options);
|
||||
// → Updates config table with CLI overrides
|
||||
// → Separate transaction after complete config exists
|
||||
|
||||
// 5. Secure Key Storage
|
||||
store_relay_private_key(relay_privkey);
|
||||
|
||||
// 6. Cache Initialization
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
### New Functions Needed
|
||||
|
||||
```c
|
||||
// In config.c
|
||||
int populate_all_config_values_atomic(const cli_options_t* cli_options) {
|
||||
// BEGIN TRANSACTION
|
||||
// Insert ALL defaults from DEFAULT_CONFIG_VALUES[]
|
||||
// Insert admin_pubkey from g_unified_cache
|
||||
// Insert relay_pubkey from g_unified_cache
|
||||
// COMMIT TRANSACTION
|
||||
return 0;
|
||||
}
|
||||
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options) {
|
||||
// BEGIN TRANSACTION
|
||||
// IF port_override: UPDATE config SET value = ? WHERE key = 'relay_port'
|
||||
// IF admin_pubkey_override: UPDATE config SET value = ? WHERE key = 'admin_pubkey'
|
||||
// IF relay_privkey_override: UPDATE config SET value = ? WHERE key = 'relay_privkey'
|
||||
// COMMIT TRANSACTION
|
||||
// invalidate_config_cache()
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scenario 2: Restart with Existing Database + CLI Options
|
||||
|
||||
### Sequence
|
||||
|
||||
```
|
||||
1. Database Discovery Phase
|
||||
├─ find_existing_db_files() → ["<relay_pubkey>.db"]
|
||||
├─ extract_pubkey_from_filename() → relay_pubkey
|
||||
└─ Sets g_database_path = "<relay_pubkey>.db"
|
||||
|
||||
2. Database Initialization Phase
|
||||
└─ init_database(g_database_path)
|
||||
└─ Opens existing database
|
||||
|
||||
3. Config Validation Phase
|
||||
└─ validate_config_table_completeness()
|
||||
├─ Check if all required keys exist
|
||||
└─ IF missing keys: populate_missing_config_values()
|
||||
|
||||
4. CLI Override Phase (ATOMIC)
|
||||
├─ BEGIN TRANSACTION
|
||||
├─ apply_cli_overrides()
|
||||
│ └─ UPDATE config SET value = ? WHERE key = ?
|
||||
└─ COMMIT TRANSACTION
|
||||
|
||||
5. Cache Initialization Phase
|
||||
└─ refresh_unified_cache_from_table()
|
||||
└─ Loads complete config into g_unified_cache
|
||||
```
|
||||
|
||||
### Function Call Sequence
|
||||
|
||||
```c
|
||||
// In main.c - existing relay branch
|
||||
else {
|
||||
// 1. Database Discovery
|
||||
char** existing_files = find_existing_db_files();
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
startup_existing_relay(relay_pubkey);
|
||||
// → Sets g_database_path
|
||||
|
||||
// 2. Database Initialization
|
||||
init_database(g_database_path);
|
||||
|
||||
// 3. Config Validation (NEW FUNCTION)
|
||||
validate_config_table_completeness();
|
||||
// → Checks for missing keys
|
||||
// → Populates any missing defaults
|
||||
|
||||
// 4. CLI Override Phase (REUSE FUNCTION)
|
||||
if (has_cli_overrides(&cli_options)) {
|
||||
apply_cli_overrides_atomic(&cli_options);
|
||||
}
|
||||
|
||||
// 5. Cache Initialization
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
### New Functions Needed
|
||||
|
||||
```c
|
||||
// In config.c
|
||||
int validate_config_table_completeness(void) {
|
||||
// Check if all DEFAULT_CONFIG_VALUES keys exist
|
||||
// IF missing: populate_missing_config_values()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int populate_missing_config_values(void) {
|
||||
// BEGIN TRANSACTION
|
||||
// For each key in DEFAULT_CONFIG_VALUES:
|
||||
// IF NOT EXISTS: INSERT INTO config
|
||||
// COMMIT TRANSACTION
|
||||
return 0;
|
||||
}
|
||||
|
||||
int has_cli_overrides(const cli_options_t* cli_options) {
|
||||
return (cli_options->port_override > 0 ||
|
||||
cli_options->admin_pubkey_override[0] != '\0' ||
|
||||
cli_options->relay_privkey_override[0] != '\0');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scenario 3: Restart with Existing Database + No CLI Options
|
||||
|
||||
### Sequence
|
||||
|
||||
```
|
||||
1. Database Discovery Phase
|
||||
├─ find_existing_db_files() → ["<relay_pubkey>.db"]
|
||||
├─ extract_pubkey_from_filename() → relay_pubkey
|
||||
└─ Sets g_database_path = "<relay_pubkey>.db"
|
||||
|
||||
2. Database Initialization Phase
|
||||
└─ init_database(g_database_path)
|
||||
└─ Opens existing database
|
||||
|
||||
3. Config Validation Phase
|
||||
└─ validate_config_table_completeness()
|
||||
├─ Check if all required keys exist
|
||||
└─ IF missing keys: populate_missing_config_values()
|
||||
|
||||
4. Cache Initialization Phase (IMMEDIATE)
|
||||
└─ refresh_unified_cache_from_table()
|
||||
└─ Loads complete config into g_unified_cache
|
||||
```
|
||||
|
||||
### Function Call Sequence
|
||||
|
||||
```c
|
||||
// In main.c - existing relay branch (no CLI overrides)
|
||||
else {
|
||||
// 1. Database Discovery
|
||||
char** existing_files = find_existing_db_files();
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
startup_existing_relay(relay_pubkey);
|
||||
|
||||
// 2. Database Initialization
|
||||
init_database(g_database_path);
|
||||
|
||||
// 3. Config Validation
|
||||
validate_config_table_completeness();
|
||||
|
||||
// 4. Cache Initialization (IMMEDIATE - no overrides to apply)
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Atomic Config Creation
|
||||
|
||||
**Before:**
|
||||
```c
|
||||
populate_default_config_values(); // Step 1
|
||||
update_config_in_table("relay_port", port_str); // Step 2
|
||||
add_pubkeys_to_config_table(); // Step 3
|
||||
```
|
||||
|
||||
**After:**
|
||||
```c
|
||||
populate_all_config_values_atomic(&cli_options); // Single transaction
|
||||
apply_cli_overrides_atomic(&cli_options); // Separate transaction
|
||||
```
|
||||
|
||||
### 2. Elimination of Race Conditions
|
||||
|
||||
**Before:**
|
||||
- Cache could refresh between steps 1-3
|
||||
- Incomplete config could be read
|
||||
|
||||
**After:**
|
||||
- Config created atomically
|
||||
- Cache only refreshed after complete config exists
|
||||
|
||||
### 3. Unified Code Path
|
||||
|
||||
**Before:**
|
||||
- Different logic for first-time vs restart
|
||||
- `populate_default_config_values()` vs `add_pubkeys_to_config_table()`
|
||||
|
||||
**After:**
|
||||
- Same validation logic for both scenarios
|
||||
- `validate_config_table_completeness()` handles both cases
|
||||
|
||||
### 4. Clear Separation of Concerns
|
||||
|
||||
**Before:**
|
||||
- CLI overrides mixed with default population
|
||||
- Unclear when overrides are applied
|
||||
|
||||
**After:**
|
||||
- Phase 1: Complete config creation
|
||||
- Phase 2: CLI overrides (if any)
|
||||
- Phase 3: Cache initialization
|
||||
|
||||
---
|
||||
|
||||
## Implementation Changes Required
|
||||
|
||||
### 1. New Functions in config.c
|
||||
|
||||
```c
|
||||
// Atomic config population for first-time startup
|
||||
int populate_all_config_values_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Atomic CLI override application
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Config validation for existing databases
|
||||
int validate_config_table_completeness(void);
|
||||
int populate_missing_config_values(void);
|
||||
|
||||
// Helper function
|
||||
int has_cli_overrides(const cli_options_t* cli_options);
|
||||
```
|
||||
|
||||
### 2. Modified Functions in config.c
|
||||
|
||||
```c
|
||||
// Simplify to only generate keys and set database path
|
||||
int first_time_startup_sequence(const cli_options_t* cli_options);
|
||||
|
||||
// Remove config population logic
|
||||
int add_pubkeys_to_config_table(void); // DEPRECATED - logic moved to populate_all_config_values_atomic()
|
||||
```
|
||||
|
||||
### 3. Modified Startup Flow in main.c
|
||||
|
||||
```c
|
||||
// First-time startup
|
||||
if (is_first_time_startup()) {
|
||||
first_time_startup_sequence(&cli_options);
|
||||
init_database(g_database_path);
|
||||
populate_all_config_values_atomic(&cli_options); // NEW
|
||||
apply_cli_overrides_atomic(&cli_options); // NEW
|
||||
store_relay_private_key(relay_privkey);
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
// Existing relay
|
||||
else {
|
||||
startup_existing_relay(relay_pubkey);
|
||||
init_database(g_database_path);
|
||||
validate_config_table_completeness(); // NEW
|
||||
if (has_cli_overrides(&cli_options)) {
|
||||
apply_cli_overrides_atomic(&cli_options); // NEW
|
||||
}
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Atomicity**: Config creation is atomic - no partial states
|
||||
2. **Simplicity**: Clear phases with single responsibility
|
||||
3. **Safety**: Cache only loaded after complete config exists
|
||||
4. **Consistency**: Same validation logic for all scenarios
|
||||
5. **Maintainability**: Easier to understand and modify
|
||||
6. **Testability**: Each phase can be tested independently
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
1. Implement new functions in config.c
|
||||
2. Update main.c startup flow
|
||||
3. Test first-time startup scenario
|
||||
4. Test restart with CLI overrides
|
||||
5. Test restart without CLI overrides
|
||||
6. Remove deprecated functions
|
||||
7. Update documentation
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Cases
|
||||
|
||||
1. **First-time startup with defaults**
|
||||
- Verify all config values created atomically
|
||||
- Verify cache loads complete config
|
||||
|
||||
2. **First-time startup with port override**
|
||||
- Verify defaults created first
|
||||
- Verify port override applied second
|
||||
- Verify cache reflects override
|
||||
|
||||
3. **Restart with complete config**
|
||||
- Verify no config changes
|
||||
- Verify cache loads immediately
|
||||
|
||||
4. **Restart with missing config keys**
|
||||
- Verify missing keys populated
|
||||
- Verify existing keys unchanged
|
||||
|
||||
5. **Restart with CLI overrides**
|
||||
- Verify overrides applied atomically
|
||||
- Verify cache invalidated and refreshed
|
||||
|
||||
### Validation Points
|
||||
|
||||
- Config table row count after each phase
|
||||
- Cache validity state after each phase
|
||||
- Transaction boundaries (BEGIN/COMMIT)
|
||||
- Error handling for failed transactions
|
||||
746
docs/unified_startup_implementation_plan.md
Normal file
746
docs/unified_startup_implementation_plan.md
Normal file
@@ -0,0 +1,746 @@
|
||||
# Unified Startup Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides a detailed implementation plan for refactoring the startup sequence to use atomic config creation followed by CLI overrides. This plan breaks down the work into discrete, testable steps.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Create New Functions in config.c
|
||||
|
||||
### Step 1.1: Implement `populate_all_config_values_atomic()`
|
||||
|
||||
**Location**: `src/config.c`
|
||||
|
||||
**Purpose**: Create complete config table in single transaction for first-time startup
|
||||
|
||||
**Function Signature**:
|
||||
```c
|
||||
int populate_all_config_values_atomic(const cli_options_t* cli_options);
|
||||
```
|
||||
|
||||
**Implementation Details**:
|
||||
```c
|
||||
int populate_all_config_values_atomic(const cli_options_t* cli_options) {
|
||||
if (!g_database) {
|
||||
DEBUG_ERROR("Database not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
char* err_msg = NULL;
|
||||
int rc = sqlite3_exec(g_database, "BEGIN TRANSACTION;", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to begin transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prepare INSERT statement
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const char* sql = "INSERT INTO config (key, value) VALUES (?, ?)";
|
||||
rc = sqlite3_prepare_v2(g_database, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert all default config values
|
||||
for (size_t i = 0; i < sizeof(DEFAULT_CONFIG_VALUES) / sizeof(DEFAULT_CONFIG_VALUES[0]); i++) {
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, DEFAULT_CONFIG_VALUES[i].key, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, DEFAULT_CONFIG_VALUES[i].value, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert config key '%s': %s",
|
||||
DEFAULT_CONFIG_VALUES[i].key, sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert admin_pubkey from cache
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, g_unified_cache.admin_pubkey, -1, SQLITE_STATIC);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert admin_pubkey: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert relay_pubkey from cache
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "relay_pubkey", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, g_unified_cache.relay_pubkey, -1, SQLITE_STATIC);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert relay_pubkey: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Commit transaction
|
||||
rc = sqlite3_exec(g_database, "COMMIT;", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to commit transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Successfully populated all config values atomically");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify transaction atomicity (all or nothing)
|
||||
- Verify all DEFAULT_CONFIG_VALUES inserted
|
||||
- Verify admin_pubkey and relay_pubkey inserted
|
||||
- Verify error handling on failure
|
||||
|
||||
---
|
||||
|
||||
### Step 1.2: Implement `apply_cli_overrides_atomic()`
|
||||
|
||||
**Location**: `src/config.c`
|
||||
|
||||
**Purpose**: Apply CLI overrides to existing config table in single transaction
|
||||
|
||||
**Function Signature**:
|
||||
```c
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options);
|
||||
```
|
||||
|
||||
**Implementation Details**:
|
||||
```c
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options) {
|
||||
if (!g_database) {
|
||||
DEBUG_ERROR("Database not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!cli_options) {
|
||||
DEBUG_ERROR("CLI options is NULL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if any overrides exist
|
||||
bool has_overrides = false;
|
||||
if (cli_options->port_override > 0) has_overrides = true;
|
||||
if (cli_options->admin_pubkey_override[0] != '\0') has_overrides = true;
|
||||
if (cli_options->relay_privkey_override[0] != '\0') has_overrides = true;
|
||||
|
||||
if (!has_overrides) {
|
||||
DEBUG_INFO("No CLI overrides to apply");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
char* err_msg = NULL;
|
||||
int rc = sqlite3_exec(g_database, "BEGIN TRANSACTION;", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to begin transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prepare UPDATE statement
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const char* sql = "UPDATE config SET value = ? WHERE key = ?";
|
||||
rc = sqlite3_prepare_v2(g_database, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Apply port override
|
||||
if (cli_options->port_override > 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%d", cli_options->port_override);
|
||||
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, port_str, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, "relay_port", -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to update relay_port: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO("Applied CLI override: relay_port = %s", port_str);
|
||||
}
|
||||
|
||||
// Apply admin_pubkey override
|
||||
if (cli_options->admin_pubkey_override[0] != '\0') {
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, cli_options->admin_pubkey_override, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, "admin_pubkey", -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to update admin_pubkey: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO("Applied CLI override: admin_pubkey");
|
||||
}
|
||||
|
||||
// Apply relay_privkey override
|
||||
if (cli_options->relay_privkey_override[0] != '\0') {
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, cli_options->relay_privkey_override, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, "relay_privkey", -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to update relay_privkey: %s", sqlite3_errmsg(g_database));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO("Applied CLI override: relay_privkey");
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Commit transaction
|
||||
rc = sqlite3_exec(g_database, "COMMIT;", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to commit transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_exec(g_database, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Invalidate cache to force refresh
|
||||
invalidate_config_cache();
|
||||
|
||||
DEBUG_INFO("Successfully applied CLI overrides atomically");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify transaction atomicity
|
||||
- Verify each override type (port, admin_pubkey, relay_privkey)
|
||||
- Verify cache invalidation after overrides
|
||||
- Verify no-op when no overrides present
|
||||
|
||||
---
|
||||
|
||||
### Step 1.3: Implement `validate_config_table_completeness()`
|
||||
|
||||
**Location**: `src/config.c`
|
||||
|
||||
**Purpose**: Validate config table has all required keys, populate missing ones
|
||||
|
||||
**Function Signature**:
|
||||
```c
|
||||
int validate_config_table_completeness(void);
|
||||
```
|
||||
|
||||
**Implementation Details**:
|
||||
```c
|
||||
int validate_config_table_completeness(void) {
|
||||
if (!g_database) {
|
||||
DEBUG_ERROR("Database not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Validating config table completeness");
|
||||
|
||||
// Check each default config key
|
||||
for (size_t i = 0; i < sizeof(DEFAULT_CONFIG_VALUES) / sizeof(DEFAULT_CONFIG_VALUES[0]); i++) {
|
||||
const char* key = DEFAULT_CONFIG_VALUES[i].key;
|
||||
|
||||
// Check if key exists
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const char* sql = "SELECT COUNT(*) FROM config WHERE key = ?";
|
||||
int rc = sqlite3_prepare_v2(g_database, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_database));
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
int count = 0;
|
||||
if (rc == SQLITE_ROW) {
|
||||
count = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// If key missing, populate it
|
||||
if (count == 0) {
|
||||
DEBUG_WARN("Config key '%s' missing, populating with default", key);
|
||||
rc = populate_missing_config_key(key, DEFAULT_CONFIG_VALUES[i].value);
|
||||
if (rc != 0) {
|
||||
DEBUG_ERROR("Failed to populate missing key '%s'", key);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO("Config table validation complete");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Helper Function**:
|
||||
```c
|
||||
static int populate_missing_config_key(const char* key, const char* value) {
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const char* sql = "INSERT INTO config (key, value) VALUES (?, ?)";
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_database, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_database));
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert config key '%s': %s", key, sqlite3_errmsg(g_database));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify detection of missing keys
|
||||
- Verify population of missing keys with defaults
|
||||
- Verify no changes when all keys present
|
||||
- Verify error handling
|
||||
|
||||
---
|
||||
|
||||
### Step 1.4: Implement `has_cli_overrides()`
|
||||
|
||||
**Location**: `src/config.c`
|
||||
|
||||
**Purpose**: Check if any CLI overrides are present
|
||||
|
||||
**Function Signature**:
|
||||
```c
|
||||
bool has_cli_overrides(const cli_options_t* cli_options);
|
||||
```
|
||||
|
||||
**Implementation Details**:
|
||||
```c
|
||||
bool has_cli_overrides(const cli_options_t* cli_options) {
|
||||
if (!cli_options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (cli_options->port_override > 0 ||
|
||||
cli_options->admin_pubkey_override[0] != '\0' ||
|
||||
cli_options->relay_privkey_override[0] != '\0');
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify returns true when any override present
|
||||
- Verify returns false when no overrides
|
||||
- Verify NULL safety
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Update Function Declarations in config.h
|
||||
|
||||
### Step 2.1: Add New Function Declarations
|
||||
|
||||
**Location**: `src/config.h`
|
||||
|
||||
**Changes**:
|
||||
```c
|
||||
// Add after existing function declarations
|
||||
|
||||
// Atomic config population for first-time startup
|
||||
int populate_all_config_values_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Atomic CLI override application
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Config validation for existing databases
|
||||
int validate_config_table_completeness(void);
|
||||
|
||||
// Helper function to check for CLI overrides
|
||||
bool has_cli_overrides(const cli_options_t* cli_options);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Refactor Startup Flow in main.c
|
||||
|
||||
### Step 3.1: Update First-Time Startup Branch
|
||||
|
||||
**Location**: `src/main.c` (around lines 1624-1740)
|
||||
|
||||
**Current Code**:
|
||||
```c
|
||||
if (is_first_time_startup()) {
|
||||
first_time_startup_sequence(&cli_options);
|
||||
init_database(g_database_path);
|
||||
|
||||
// Current incremental approach
|
||||
populate_default_config_values();
|
||||
if (cli_options.port_override > 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
|
||||
update_config_in_table("relay_port", port_str);
|
||||
}
|
||||
add_pubkeys_to_config_table();
|
||||
|
||||
store_relay_private_key(relay_privkey);
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
**New Code**:
|
||||
```c
|
||||
if (is_first_time_startup()) {
|
||||
// 1. Generate keys and set database path
|
||||
first_time_startup_sequence(&cli_options);
|
||||
|
||||
// 2. Create database with schema
|
||||
init_database(g_database_path);
|
||||
|
||||
// 3. Populate ALL config values atomically (defaults + pubkeys)
|
||||
if (populate_all_config_values_atomic(&cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to populate config values");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// 4. Apply CLI overrides atomically (separate transaction)
|
||||
if (apply_cli_overrides_atomic(&cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to apply CLI overrides");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// 5. Store relay private key securely
|
||||
store_relay_private_key(relay_privkey);
|
||||
|
||||
// 6. Load complete config into cache
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify first-time startup creates complete config
|
||||
- Verify CLI overrides applied correctly
|
||||
- Verify cache loads complete config
|
||||
- Verify error handling at each step
|
||||
|
||||
---
|
||||
|
||||
### Step 3.2: Update Existing Relay Startup Branch
|
||||
|
||||
**Location**: `src/main.c` (around lines 1741-1928)
|
||||
|
||||
**Current Code**:
|
||||
```c
|
||||
else {
|
||||
char** existing_files = find_existing_db_files();
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
startup_existing_relay(relay_pubkey);
|
||||
|
||||
init_database(g_database_path);
|
||||
|
||||
// Current approach - unclear when overrides applied
|
||||
populate_default_config_values();
|
||||
if (cli_options.port_override > 0) {
|
||||
// ... override logic ...
|
||||
}
|
||||
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
**New Code**:
|
||||
```c
|
||||
else {
|
||||
// 1. Discover existing database
|
||||
char** existing_files = find_existing_db_files();
|
||||
if (!existing_files || !existing_files[0]) {
|
||||
DEBUG_ERROR("No existing database files found");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
startup_existing_relay(relay_pubkey);
|
||||
|
||||
// 2. Open existing database
|
||||
init_database(g_database_path);
|
||||
|
||||
// 3. Validate config table completeness (populate missing keys)
|
||||
if (validate_config_table_completeness() != 0) {
|
||||
DEBUG_ERROR("Failed to validate config table");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// 4. Apply CLI overrides if present (separate transaction)
|
||||
if (has_cli_overrides(&cli_options)) {
|
||||
if (apply_cli_overrides_atomic(&cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to apply CLI overrides");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Load complete config into cache
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
- Verify existing relay startup with complete config
|
||||
- Verify missing keys populated
|
||||
- Verify CLI overrides applied when present
|
||||
- Verify no changes when no overrides
|
||||
- Verify cache loads correctly
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Deprecate Old Functions
|
||||
|
||||
### Step 4.1: Mark Functions as Deprecated
|
||||
|
||||
**Location**: `src/config.c`
|
||||
|
||||
**Functions to Deprecate**:
|
||||
1. `populate_default_config_values()` - replaced by `populate_all_config_values_atomic()`
|
||||
2. `add_pubkeys_to_config_table()` - logic moved to `populate_all_config_values_atomic()`
|
||||
|
||||
**Changes**:
|
||||
```c
|
||||
// Mark as deprecated in comments
|
||||
// DEPRECATED: Use populate_all_config_values_atomic() instead
|
||||
// This function will be removed in a future version
|
||||
int populate_default_config_values(void) {
|
||||
// ... existing implementation ...
|
||||
}
|
||||
|
||||
// DEPRECATED: Use populate_all_config_values_atomic() instead
|
||||
// This function will be removed in a future version
|
||||
int add_pubkeys_to_config_table(void) {
|
||||
// ... existing implementation ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
1. **Test `populate_all_config_values_atomic()`**
|
||||
- Test with valid cli_options
|
||||
- Test transaction rollback on error
|
||||
- Test all config keys inserted
|
||||
- Test pubkeys inserted correctly
|
||||
|
||||
2. **Test `apply_cli_overrides_atomic()`**
|
||||
- Test port override
|
||||
- Test admin_pubkey override
|
||||
- Test relay_privkey override
|
||||
- Test multiple overrides
|
||||
- Test no overrides
|
||||
- Test transaction rollback on error
|
||||
|
||||
3. **Test `validate_config_table_completeness()`**
|
||||
- Test with complete config
|
||||
- Test with missing keys
|
||||
- Test population of missing keys
|
||||
|
||||
4. **Test `has_cli_overrides()`**
|
||||
- Test with each override type
|
||||
- Test with no overrides
|
||||
- Test with NULL cli_options
|
||||
|
||||
### Integration Tests
|
||||
|
||||
1. **First-Time Startup**
|
||||
```bash
|
||||
# Clean environment
|
||||
rm -f *.db
|
||||
|
||||
# Start relay with defaults
|
||||
./build/c_relay_x86
|
||||
|
||||
# Verify config table complete
|
||||
sqlite3 <relay_pubkey>.db "SELECT COUNT(*) FROM config;"
|
||||
# Expected: 20+ rows (all defaults + pubkeys)
|
||||
|
||||
# Verify cache loaded
|
||||
# Check relay.log for cache refresh message
|
||||
```
|
||||
|
||||
2. **First-Time Startup with CLI Overrides**
|
||||
```bash
|
||||
# Clean environment
|
||||
rm -f *.db
|
||||
|
||||
# Start relay with port override
|
||||
./build/c_relay_x86 --port 9999
|
||||
|
||||
# Verify port override applied
|
||||
sqlite3 <relay_pubkey>.db "SELECT value FROM config WHERE key='relay_port';"
|
||||
# Expected: 9999
|
||||
```
|
||||
|
||||
3. **Restart with Existing Database**
|
||||
```bash
|
||||
# Start relay (creates database)
|
||||
./build/c_relay_x86
|
||||
|
||||
# Stop relay
|
||||
pkill -f c_relay_
|
||||
|
||||
# Restart relay
|
||||
./build/c_relay_x86
|
||||
|
||||
# Verify config unchanged
|
||||
# Check relay.log for validation message
|
||||
```
|
||||
|
||||
4. **Restart with CLI Overrides**
|
||||
```bash
|
||||
# Start relay (creates database)
|
||||
./build/c_relay_x86
|
||||
|
||||
# Stop relay
|
||||
pkill -f c_relay_
|
||||
|
||||
# Restart with port override
|
||||
./build/c_relay_x86 --port 9999
|
||||
|
||||
# Verify port override applied
|
||||
sqlite3 <relay_pubkey>.db "SELECT value FROM config WHERE key='relay_port';"
|
||||
# Expected: 9999
|
||||
```
|
||||
|
||||
### Regression Tests
|
||||
|
||||
Run existing test suite to ensure no breakage:
|
||||
```bash
|
||||
./tests/run_all_tests.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Documentation Updates
|
||||
|
||||
### Files to Update
|
||||
|
||||
1. **docs/configuration_guide.md**
|
||||
- Update startup sequence description
|
||||
- Document new atomic config creation
|
||||
- Document CLI override behavior
|
||||
|
||||
2. **docs/startup_flows_complete.md**
|
||||
- Update with new flow diagrams
|
||||
- Document new function calls
|
||||
|
||||
3. **README.md**
|
||||
- Update CLI options documentation
|
||||
- Document override behavior
|
||||
|
||||
---
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Week 1: Core Functions
|
||||
- Day 1-2: Implement `populate_all_config_values_atomic()`
|
||||
- Day 3-4: Implement `apply_cli_overrides_atomic()`
|
||||
- Day 5: Implement `validate_config_table_completeness()` and `has_cli_overrides()`
|
||||
|
||||
### Week 2: Integration
|
||||
- Day 1-2: Update main.c startup flow
|
||||
- Day 3-4: Testing and bug fixes
|
||||
- Day 5: Documentation updates
|
||||
|
||||
### Week 3: Cleanup
|
||||
- Day 1-2: Deprecate old functions
|
||||
- Day 3-4: Final testing and validation
|
||||
- Day 5: Code review and merge
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Potential Issues
|
||||
|
||||
1. **Database Lock Contention**
|
||||
- Risk: Multiple transactions could cause locks
|
||||
- Mitigation: Use BEGIN IMMEDIATE for write transactions
|
||||
|
||||
2. **Cache Invalidation Timing**
|
||||
- Risk: Cache could be read before overrides applied
|
||||
- Mitigation: Invalidate cache immediately after overrides
|
||||
|
||||
3. **Backward Compatibility**
|
||||
- Risk: Existing databases might have incomplete config
|
||||
- Mitigation: `validate_config_table_completeness()` handles this
|
||||
|
||||
4. **Transaction Rollback**
|
||||
- Risk: Partial config on error
|
||||
- Mitigation: All operations in transactions with proper rollback
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✅ All config values created atomically in first-time startup
|
||||
2. ✅ CLI overrides applied in separate atomic transaction
|
||||
3. ✅ Existing databases validated and missing keys populated
|
||||
4. ✅ Cache only loaded after complete config exists
|
||||
5. ✅ All existing tests pass
|
||||
6. ✅ No race conditions in config creation
|
||||
7. ✅ Clear separation between config creation and override phases
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise during implementation:
|
||||
|
||||
1. **Revert main.c changes** - restore original startup flow
|
||||
2. **Keep new functions** - they can coexist with old code
|
||||
3. **Add feature flag** - allow toggling between old and new behavior
|
||||
4. **Gradual migration** - enable new behavior per scenario
|
||||
|
||||
```c
|
||||
// Feature flag approach
|
||||
#define USE_ATOMIC_CONFIG_CREATION 1
|
||||
|
||||
#if USE_ATOMIC_CONFIG_CREATION
|
||||
// New atomic approach
|
||||
populate_all_config_values_atomic(&cli_options);
|
||||
apply_cli_overrides_atomic(&cli_options);
|
||||
#else
|
||||
// Old incremental approach
|
||||
populate_default_config_values();
|
||||
// ... existing code ...
|
||||
#endif
|
||||
```
|
||||
@@ -1,140 +0,0 @@
|
||||
# Why MUSL Compilation Fails: Technical Explanation
|
||||
|
||||
## The Core Problem
|
||||
|
||||
**You cannot mix glibc headers/libraries with MUSL's C library.** They are fundamentally incompatible at the ABI (Application Binary Interface) level.
|
||||
|
||||
## What Happens When We Try
|
||||
|
||||
```bash
|
||||
musl-gcc -I/usr/include src/main.c -lwebsockets
|
||||
```
|
||||
|
||||
### Step-by-Step Breakdown:
|
||||
|
||||
1. **musl-gcc includes `<libwebsockets.h>`** from `/usr/include/libwebsockets.h`
|
||||
|
||||
2. **libwebsockets.h includes standard C headers:**
|
||||
```c
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
```
|
||||
|
||||
3. **The system provides glibc's version of these headers** (from `/usr/include/`)
|
||||
|
||||
4. **glibc's `<string.h>` includes glibc-specific internal headers:**
|
||||
```c
|
||||
#include <bits/libc-header-start.h>
|
||||
#include <bits/types.h>
|
||||
```
|
||||
|
||||
5. **MUSL doesn't have these `bits/` headers** - it has a completely different structure:
|
||||
- MUSL uses `/usr/include/x86_64-linux-musl/` for its headers
|
||||
- MUSL's headers are simpler and don't use the `bits/` subdirectory structure
|
||||
|
||||
6. **Compilation fails** with:
|
||||
```
|
||||
fatal error: bits/libc-header-start.h: No such file or directory
|
||||
```
|
||||
|
||||
## Why This Is Fundamental
|
||||
|
||||
### Different C Library Implementations
|
||||
|
||||
**glibc (GNU C Library):**
|
||||
- Complex, feature-rich implementation
|
||||
- Uses `bits/` subdirectories for platform-specific code
|
||||
- Larger binary size
|
||||
- More system-specific optimizations
|
||||
|
||||
**MUSL:**
|
||||
- Minimal, clean implementation
|
||||
- Simpler header structure
|
||||
- Smaller binary size
|
||||
- Designed for static linking and portability
|
||||
|
||||
### ABI Incompatibility
|
||||
|
||||
Even if headers compiled, the **Application Binary Interface (ABI)** is different:
|
||||
- Function calling conventions may differ
|
||||
- Structure layouts may differ
|
||||
- System call wrappers are implemented differently
|
||||
- Thread-local storage mechanisms differ
|
||||
|
||||
## The Solution: Build Everything with MUSL
|
||||
|
||||
To create a true MUSL static binary, you must:
|
||||
|
||||
### 1. Build libwebsockets with musl-gcc
|
||||
|
||||
```bash
|
||||
git clone https://github.com/warmcat/libwebsockets.git
|
||||
cd libwebsockets
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_C_COMPILER=musl-gcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITHOUT_TESTAPPS=ON
|
||||
make
|
||||
```
|
||||
|
||||
### 2. Build OpenSSL with MUSL
|
||||
|
||||
```bash
|
||||
wget https://www.openssl.org/source/openssl-3.0.0.tar.gz
|
||||
tar xzf openssl-3.0.0.tar.gz
|
||||
cd openssl-3.0.0
|
||||
CC=musl-gcc ./config no-shared --prefix=/opt/musl-openssl
|
||||
make && make install
|
||||
```
|
||||
|
||||
### 3. Build all other dependencies
|
||||
|
||||
- zlib with musl-gcc
|
||||
- libsecp256k1 with musl-gcc
|
||||
- libcurl with musl-gcc (which itself needs OpenSSL built with MUSL)
|
||||
|
||||
### 4. Build c-relay with all MUSL libraries
|
||||
|
||||
```bash
|
||||
musl-gcc -static \
|
||||
-I/opt/musl-libwebsockets/include \
|
||||
-I/opt/musl-openssl/include \
|
||||
src/*.c \
|
||||
-L/opt/musl-libwebsockets/lib -lwebsockets \
|
||||
-L/opt/musl-openssl/lib -lssl -lcrypto \
|
||||
...
|
||||
```
|
||||
|
||||
## Why We Use glibc Static Instead
|
||||
|
||||
Building the entire dependency chain with MUSL is:
|
||||
- **Time-consuming**: Hours to build all dependencies
|
||||
- **Complex**: Each library has its own build quirks
|
||||
- **Maintenance burden**: Must rebuild when dependencies update
|
||||
- **Unnecessary for most use cases**: glibc static binaries work fine
|
||||
|
||||
### glibc Static Binary Advantages:
|
||||
|
||||
✅ **Still fully static** - no runtime dependencies
|
||||
✅ **Works on virtually all Linux distributions**
|
||||
✅ **Much faster to build** - uses system libraries
|
||||
✅ **Easier to maintain** - no custom dependency builds
|
||||
✅ **Same practical portability** for modern Linux systems
|
||||
|
||||
### glibc Static Binary Limitations:
|
||||
|
||||
⚠️ **Slightly larger** than MUSL (glibc is bigger)
|
||||
⚠️ **May not work on very old systems** (ancient glibc versions)
|
||||
⚠️ **Not as universally portable** as MUSL (but close enough)
|
||||
|
||||
## Conclusion
|
||||
|
||||
**MUSL compilation fails because system libraries are compiled with glibc, and you cannot mix glibc and MUSL.**
|
||||
|
||||
The current approach (glibc static binary) is the pragmatic solution that provides excellent portability without the complexity of building an entire MUSL toolchain.
|
||||
|
||||
If true MUSL binaries are needed in the future, the solution is to use Alpine Linux (which uses MUSL natively) in a Docker container, where all system libraries are already MUSL-compiled.
|
||||
@@ -12,6 +12,7 @@ USE_TEST_KEYS=false
|
||||
ADMIN_KEY=""
|
||||
RELAY_KEY=""
|
||||
PORT_OVERRIDE=""
|
||||
DEBUG_LEVEL="5"
|
||||
|
||||
# Key validation function
|
||||
validate_hex_key() {
|
||||
@@ -71,6 +72,34 @@ while [[ $# -gt 0 ]]; do
|
||||
USE_TEST_KEYS=true
|
||||
shift
|
||||
;;
|
||||
--debug-level=*)
|
||||
DEBUG_LEVEL="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
-d=*)
|
||||
DEBUG_LEVEL="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--debug-level)
|
||||
if [ -z "$2" ]; then
|
||||
echo "ERROR: Debug level option requires a value"
|
||||
HELP=true
|
||||
shift
|
||||
else
|
||||
DEBUG_LEVEL="$2"
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
-d)
|
||||
if [ -z "$2" ]; then
|
||||
echo "ERROR: Debug level option requires a value"
|
||||
HELP=true
|
||||
shift
|
||||
else
|
||||
DEBUG_LEVEL="$2"
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
--help|-h)
|
||||
HELP=true
|
||||
shift
|
||||
@@ -104,6 +133,14 @@ if [ -n "$PORT_OVERRIDE" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate debug level if provided
|
||||
if [ -n "$DEBUG_LEVEL" ]; then
|
||||
if ! [[ "$DEBUG_LEVEL" =~ ^[0-5]$ ]]; then
|
||||
echo "ERROR: Debug level must be 0-5, got: $DEBUG_LEVEL"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show help
|
||||
if [ "$HELP" = true ]; then
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
@@ -112,6 +149,7 @@ if [ "$HELP" = true ]; then
|
||||
echo " -a, --admin-key <hex> 64-character hex admin private key"
|
||||
echo " -r, --relay-key <hex> 64-character hex relay private key"
|
||||
echo " -p, --port <port> Custom port override (default: 8888)"
|
||||
echo " -d, --debug-level <0-5> Set debug level: 0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace"
|
||||
echo " --preserve-database Keep existing database files (don't delete for fresh start)"
|
||||
echo " --test-keys, -t Use deterministic test keys for development (admin: all 'a's, relay: all '1's)"
|
||||
echo " --help, -h Show this help message"
|
||||
@@ -125,6 +163,8 @@ if [ "$HELP" = true ]; then
|
||||
echo " $0 # Fresh start with random keys"
|
||||
echo " $0 -a <admin-hex> -r <relay-hex> # Use custom keys"
|
||||
echo " $0 -a <admin-hex> -p 9000 # Custom admin key on port 9000"
|
||||
echo " $0 --debug-level=3 # Start with debug level 3 (info)"
|
||||
echo " $0 -d=5 # Start with debug level 5 (trace)"
|
||||
echo " $0 --preserve-database # Preserve existing database and keys"
|
||||
echo " $0 --test-keys # Use test keys for consistent development"
|
||||
echo " $0 -t --preserve-database # Use test keys and preserve database"
|
||||
@@ -280,19 +320,24 @@ if [ -n "$PORT_OVERRIDE" ]; then
|
||||
echo "Using custom port: $PORT_OVERRIDE"
|
||||
fi
|
||||
|
||||
if [ -n "$DEBUG_LEVEL" ]; then
|
||||
RELAY_ARGS="$RELAY_ARGS --debug-level=$DEBUG_LEVEL"
|
||||
echo "Using debug level: $DEBUG_LEVEL"
|
||||
fi
|
||||
|
||||
# Change to build directory before starting relay so database files are created there
|
||||
cd build
|
||||
# Start relay in background and capture its PID
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
echo "Using deterministic test keys for development..."
|
||||
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
elif [ -n "$RELAY_ARGS" ]; then
|
||||
echo "Starting relay with custom configuration..."
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
else
|
||||
# No command line arguments needed for random key generation
|
||||
echo "Starting relay with random key generation..."
|
||||
./$(basename $BINARY_PATH) --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
fi
|
||||
RELAY_PID=$!
|
||||
# Change back to original directory
|
||||
|
||||
18
src/api.c
18
src/api.c
@@ -10,13 +10,7 @@
|
||||
#include "api.h"
|
||||
#include "embedded_web_content.h"
|
||||
#include "config.h"
|
||||
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
#include "debug.h"
|
||||
|
||||
// Forward declarations for database functions
|
||||
int store_event(cJSON* event);
|
||||
@@ -35,7 +29,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
snprintf(temp_path, sizeof(temp_path), "/%s", requested_uri + 5); // Add leading slash
|
||||
file_path = temp_path;
|
||||
} else {
|
||||
log_warning("Embedded file request without /api prefix");
|
||||
DEBUG_WARN("Embedded file request without /api prefix");
|
||||
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
|
||||
return -1;
|
||||
}
|
||||
@@ -43,7 +37,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
// Get embedded file
|
||||
embedded_file_t* file = get_embedded_file(file_path);
|
||||
if (!file) {
|
||||
log_warning("Embedded file not found");
|
||||
DEBUG_WARN("Embedded file not found");
|
||||
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
|
||||
return -1;
|
||||
}
|
||||
@@ -51,7 +45,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
// Allocate session data
|
||||
struct embedded_file_session_data* session_data = malloc(sizeof(struct embedded_file_session_data));
|
||||
if (!session_data) {
|
||||
log_error("Failed to allocate embedded file session data");
|
||||
DEBUG_ERROR("Failed to allocate embedded file session data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -135,7 +129,7 @@ int handle_embedded_file_writeable(struct lws* wsi) {
|
||||
// Allocate buffer for data transmission
|
||||
unsigned char *buf = malloc(LWS_PRE + session_data->size);
|
||||
if (!buf) {
|
||||
log_error("Failed to allocate buffer for embedded file transmission");
|
||||
DEBUG_ERROR("Failed to allocate buffer for embedded file transmission");
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
return -1;
|
||||
@@ -151,7 +145,7 @@ int handle_embedded_file_writeable(struct lws* wsi) {
|
||||
free(buf);
|
||||
|
||||
if (write_result < 0) {
|
||||
log_error("Failed to write embedded file data");
|
||||
DEBUG_ERROR("Failed to write embedded file data");
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
return -1;
|
||||
|
||||
929
src/config.c
929
src/config.c
File diff suppressed because it is too large
Load Diff
@@ -105,6 +105,7 @@ typedef struct {
|
||||
char admin_pubkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
||||
int debug_level; // 0-5, default 0 (no debug output)
|
||||
} cli_options_t;
|
||||
|
||||
// Global unified configuration cache
|
||||
@@ -138,7 +139,7 @@ int get_config_bool(const char* key, int default_value);
|
||||
// First-time startup functions
|
||||
int is_first_time_startup(void);
|
||||
int first_time_startup_sequence(const cli_options_t* cli_options);
|
||||
int startup_existing_relay(const char* relay_pubkey);
|
||||
int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_options);
|
||||
|
||||
// Configuration application functions
|
||||
int apply_configuration_from_event(const cJSON* event);
|
||||
@@ -168,6 +169,7 @@ int set_config_value_in_table(const char* key, const char* value, const char* da
|
||||
const char* description, const char* category, int requires_restart);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
int populate_default_config_values(void);
|
||||
int populate_all_config_values_atomic(const char* admin_pubkey, const char* relay_pubkey);
|
||||
int add_pubkeys_to_config_table(void);
|
||||
|
||||
// Admin event processing functions (updated with WebSocket support)
|
||||
@@ -211,6 +213,9 @@ int populate_config_table_from_event(const cJSON* event);
|
||||
int process_startup_config_event(const cJSON* event);
|
||||
int process_startup_config_event_with_fallback(const cJSON* event);
|
||||
|
||||
// Atomic CLI override application
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Dynamic event generation functions for WebSocket configuration fetching
|
||||
cJSON* generate_config_event_from_table(void);
|
||||
int req_filter_requests_config_events(const cJSON* filter);
|
||||
|
||||
51
src/debug.c
Normal file
51
src/debug.c
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "debug.h"
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
// Global debug level (default: no debug output)
|
||||
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
|
||||
|
||||
void debug_init(int level) {
|
||||
if (level < 0) level = 0;
|
||||
if (level > 5) level = 5;
|
||||
g_debug_level = (debug_level_t)level;
|
||||
}
|
||||
|
||||
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
|
||||
// Get timestamp
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
char timestamp[32];
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
// Get level string
|
||||
const char* level_str = "UNKNOWN";
|
||||
switch (level) {
|
||||
case DEBUG_LEVEL_ERROR: level_str = "ERROR"; break;
|
||||
case DEBUG_LEVEL_WARN: level_str = "WARN "; break;
|
||||
case DEBUG_LEVEL_INFO: level_str = "INFO "; break;
|
||||
case DEBUG_LEVEL_DEBUG: level_str = "DEBUG"; break;
|
||||
case DEBUG_LEVEL_TRACE: level_str = "TRACE"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Print prefix with timestamp and level
|
||||
printf("[%s] [%s] ", timestamp, level_str);
|
||||
|
||||
// Print source location when debug level is TRACE (5) or higher
|
||||
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
|
||||
// Extract just the filename (not full path)
|
||||
const char* filename = strrchr(file, '/');
|
||||
filename = filename ? filename + 1 : file;
|
||||
printf("[%s:%d] ", filename, line);
|
||||
}
|
||||
|
||||
// Print message
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
43
src/debug.h
Normal file
43
src/debug.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
// Debug levels
|
||||
typedef enum {
|
||||
DEBUG_LEVEL_NONE = 0,
|
||||
DEBUG_LEVEL_ERROR = 1,
|
||||
DEBUG_LEVEL_WARN = 2,
|
||||
DEBUG_LEVEL_INFO = 3,
|
||||
DEBUG_LEVEL_DEBUG = 4,
|
||||
DEBUG_LEVEL_TRACE = 5
|
||||
} debug_level_t;
|
||||
|
||||
// Global debug level (set at runtime via CLI)
|
||||
extern debug_level_t g_debug_level;
|
||||
|
||||
// Initialize debug system
|
||||
void debug_init(int level);
|
||||
|
||||
// Core logging function
|
||||
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...);
|
||||
|
||||
// Convenience macros that check level before calling
|
||||
// Note: TRACE level (5) and above include file:line information for ALL messages
|
||||
#define DEBUG_ERROR(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_ERROR) debug_log(DEBUG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_WARN(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_WARN) debug_log(DEBUG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_INFO(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_LOG(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_DEBUG) debug_log(DEBUG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#define DEBUG_TRACE(...) \
|
||||
do { if (g_debug_level >= DEBUG_LEVEL_TRACE) debug_log(DEBUG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__); } while(0)
|
||||
|
||||
#endif /* DEBUG_H */
|
||||
@@ -1,5 +1,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip017.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip044.h"
|
||||
@@ -15,12 +16,6 @@
|
||||
// External database connection (from main.c)
|
||||
extern sqlite3* g_db;
|
||||
|
||||
// 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);
|
||||
|
||||
// Forward declarations for unified handlers
|
||||
extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
@@ -35,11 +30,9 @@ extern const char* get_first_tag_name(cJSON* event);
|
||||
extern const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);
|
||||
|
||||
// Forward declarations for config functions
|
||||
extern const char* get_relay_pubkey_cached(void);
|
||||
extern char* get_relay_private_key(void);
|
||||
extern const char* get_config_value(const char* key);
|
||||
extern int get_config_bool(const char* key, int default_value);
|
||||
extern const char* get_admin_pubkey_cached(void);
|
||||
|
||||
// Forward declarations for database functions
|
||||
extern int store_event(cJSON* event);
|
||||
@@ -137,14 +130,14 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
// This handles commands sent as direct JSON arrays, not wrapped in inner events
|
||||
int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
if (!command_array || !cJSON_IsArray(command_array) || !event) {
|
||||
log_error("DM Admin: Invalid command array or event");
|
||||
DEBUG_ERROR("DM Admin: Invalid command array or event");
|
||||
snprintf(error_message, error_size, "invalid: null command array or event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int array_size = cJSON_GetArraySize(command_array);
|
||||
if (array_size < 1) {
|
||||
log_error("DM Admin: Empty command array");
|
||||
DEBUG_ERROR("DM Admin: Empty command array");
|
||||
snprintf(error_message, error_size, "invalid: empty command array");
|
||||
return -1;
|
||||
}
|
||||
@@ -152,7 +145,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
// Get the command type from the first element
|
||||
cJSON* command_item = cJSON_GetArrayItem(command_array, 0);
|
||||
if (!command_item || !cJSON_IsString(command_item)) {
|
||||
log_error("DM Admin: First element is not a string command");
|
||||
DEBUG_ERROR("DM Admin: First element is not a string command");
|
||||
snprintf(error_message, error_size, "invalid: command must be a string");
|
||||
return -1;
|
||||
}
|
||||
@@ -209,7 +202,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
if (strcmp(command_type, "auth_query") == 0) {
|
||||
const char* query_type = get_tag_value(event, "auth_query", 1);
|
||||
if (!query_type) {
|
||||
log_error("DM Admin: Missing auth_query type parameter");
|
||||
DEBUG_ERROR("DM Admin: Missing auth_query type parameter");
|
||||
snprintf(error_message, error_size, "invalid: missing auth_query type");
|
||||
} else {
|
||||
result = handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
@@ -218,7 +211,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
else if (strcmp(command_type, "config_query") == 0) {
|
||||
const char* query_type = get_tag_value(event, "config_query", 1);
|
||||
if (!query_type) {
|
||||
log_error("DM Admin: Missing config_query type parameter");
|
||||
DEBUG_ERROR("DM Admin: Missing config_query type parameter");
|
||||
snprintf(error_message, error_size, "invalid: missing config_query type");
|
||||
} else {
|
||||
result = handle_config_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
@@ -228,7 +221,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
const char* config_key = get_tag_value(event, "config_set", 1);
|
||||
const char* config_value = get_tag_value(event, "config_set", 2);
|
||||
if (!config_key || !config_value) {
|
||||
log_error("DM Admin: Missing config_set parameters");
|
||||
DEBUG_ERROR("DM Admin: Missing config_set parameters");
|
||||
snprintf(error_message, error_size, "invalid: missing config_set key or value");
|
||||
} else {
|
||||
result = handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
|
||||
@@ -240,7 +233,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
else if (strcmp(command_type, "system_command") == 0) {
|
||||
const char* command = get_tag_value(event, "system_command", 1);
|
||||
if (!command) {
|
||||
log_error("DM Admin: Missing system_command type parameter");
|
||||
DEBUG_ERROR("DM Admin: Missing system_command type parameter");
|
||||
snprintf(error_message, error_size, "invalid: missing system_command type");
|
||||
} else {
|
||||
result = handle_system_command_unified(event, command, error_message, error_size, wsi);
|
||||
@@ -253,13 +246,13 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
result = handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else {
|
||||
log_error("DM Admin: Unknown command type");
|
||||
DEBUG_ERROR("DM Admin: Unknown command type");
|
||||
printf(" Unknown command: %s\n", command_type);
|
||||
snprintf(error_message, error_size, "invalid: unknown DM command type '%s'", command_type);
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
log_error("DM Admin: Command processing failed");
|
||||
DEBUG_ERROR("DM Admin: Command processing failed");
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -592,7 +585,7 @@ int apply_config_change(const char* key, const char* value) {
|
||||
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
log_error("Database not available for config change");
|
||||
DEBUG_ERROR("Database not available for config change");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -628,9 +621,9 @@ int apply_config_change(const char* key, const char* value) {
|
||||
const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
log_error("Failed to prepare config update statement");
|
||||
DEBUG_ERROR("Failed to prepare config update statement");
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
log_error(err_msg);
|
||||
DEBUG_ERROR(err_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -640,9 +633,9 @@ int apply_config_change(const char* key, const char* value) {
|
||||
|
||||
int result = sqlite3_step(stmt);
|
||||
if (result != SQLITE_DONE) {
|
||||
log_error("Failed to update configuration in database");
|
||||
DEBUG_ERROR("Failed to update configuration in database");
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
log_error(err_msg);
|
||||
DEBUG_ERROR(err_msg);
|
||||
sqlite3_finalize(stmt);
|
||||
return -1;
|
||||
}
|
||||
@@ -766,7 +759,7 @@ int handle_config_confirmation(const char* admin_pubkey, const char* response) {
|
||||
char error_msg[256];
|
||||
int send_result = send_nip17_response(admin_pubkey, success_msg, error_msg, sizeof(error_msg));
|
||||
if (send_result != 0) {
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
}
|
||||
|
||||
// Remove the pending change
|
||||
@@ -788,7 +781,7 @@ int handle_config_confirmation(const char* admin_pubkey, const char* response) {
|
||||
char send_error_msg[256];
|
||||
int send_result = send_nip17_response(admin_pubkey, error_msg, send_error_msg, sizeof(send_error_msg));
|
||||
if (send_result != 0) {
|
||||
log_error(send_error_msg);
|
||||
DEBUG_ERROR(send_error_msg);
|
||||
}
|
||||
|
||||
// Remove the pending change
|
||||
@@ -890,7 +883,7 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
|
||||
char error_msg[256];
|
||||
int send_result = send_nip17_response(admin_pubkey, confirmation, error_msg, sizeof(error_msg));
|
||||
if (send_result != 0) {
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
}
|
||||
free(confirmation);
|
||||
}
|
||||
@@ -903,7 +896,7 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
|
||||
char* generate_stats_json(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
log_error("Database not available for stats generation");
|
||||
DEBUG_ERROR("Database not available for stats generation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1007,7 +1000,7 @@ char* generate_stats_json(void) {
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (!json_string) {
|
||||
log_error("Failed to generate stats JSON");
|
||||
DEBUG_ERROR("Failed to generate stats JSON");
|
||||
}
|
||||
|
||||
return json_string;
|
||||
@@ -1024,7 +1017,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
}
|
||||
|
||||
// Get relay keys for signing
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
char* relay_privkey_hex = get_relay_private_key();
|
||||
if (!relay_pubkey || !relay_privkey_hex) {
|
||||
if (relay_privkey_hex) free(relay_privkey_hex);
|
||||
@@ -1113,14 +1106,14 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
char* generate_config_text(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
log_error("NIP-17: Database not available for config query");
|
||||
DEBUG_ERROR("NIP-17: Database not available for config query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Build comprehensive config text from database
|
||||
char* config_text = malloc(8192);
|
||||
if (!config_text) {
|
||||
log_error("NIP-17: Failed to allocate memory for config text");
|
||||
DEBUG_ERROR("NIP-17: Failed to allocate memory for config text");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1146,7 +1139,7 @@ char* generate_config_text(void) {
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
free(config_text);
|
||||
log_error("NIP-17: Failed to query config from database");
|
||||
DEBUG_ERROR("NIP-17: Failed to query config from database");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1161,7 +1154,7 @@ char* generate_config_text(void) {
|
||||
char* generate_stats_text(void) {
|
||||
char* stats_json = generate_stats_json();
|
||||
if (!stats_json) {
|
||||
log_error("NIP-17: Failed to generate stats for plain text command");
|
||||
DEBUG_ERROR("NIP-17: Failed to generate stats for plain text command");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1345,7 +1338,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
// Convert hex private key to bytes
|
||||
unsigned char relay_privkey[32];
|
||||
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
|
||||
log_error("NIP-17: Failed to convert relay private key from hex");
|
||||
DEBUG_ERROR("NIP-17: Failed to convert relay private key from hex");
|
||||
free(relay_privkey_hex);
|
||||
strncpy(error_message, "NIP-17: Failed to convert relay private key", error_size - 1);
|
||||
return NULL;
|
||||
@@ -1355,13 +1348,13 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
// Step 3: Decrypt and parse inner event using library function
|
||||
cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey);
|
||||
if (!inner_dm) {
|
||||
log_error("NIP-17: nostr_nip17_receive_dm returned NULL");
|
||||
DEBUG_ERROR("NIP-17: nostr_nip17_receive_dm returned NULL");
|
||||
// Debug: Print the gift wrap event
|
||||
char* gift_wrap_debug = cJSON_Print(gift_wrap_event);
|
||||
if (gift_wrap_debug) {
|
||||
char debug_msg[1024];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "NIP-17: Gift wrap event: %.500s", gift_wrap_debug);
|
||||
log_error(debug_msg);
|
||||
DEBUG_ERROR(debug_msg);
|
||||
free(gift_wrap_debug);
|
||||
}
|
||||
// Debug: Check if private key is valid
|
||||
@@ -1435,7 +1428,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
"[\"command_processed\", \"success\", \"%s\"]", "NIP-17 admin command executed");
|
||||
|
||||
// Get relay pubkey for creating DM event
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
cJSON* success_dm = nostr_nip17_create_chat_event(
|
||||
response_content, // message content
|
||||
@@ -1523,9 +1516,9 @@ int is_nip17_gift_wrap_for_relay(cJSON* event) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey) {
|
||||
log_error("NIP-17: Could not get relay pubkey for validation");
|
||||
DEBUG_ERROR("NIP-17: Could not get relay pubkey for validation");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1571,7 +1564,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
|
||||
|
||||
// Check if sender is admin
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0;
|
||||
|
||||
// Parse DM content as JSON array of commands
|
||||
@@ -1605,7 +1598,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
free(stats_text);
|
||||
|
||||
if (result != 0) {
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1623,7 +1616,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
free(config_text);
|
||||
|
||||
if (result != 0) {
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1657,7 +1650,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
if (config_result > 0) {
|
||||
return 1; // Return positive value to indicate response was handled
|
||||
} else {
|
||||
log_error("NIP-17: Configuration change request failed");
|
||||
DEBUG_ERROR("NIP-17: Configuration change request failed");
|
||||
return -1; // Return error to prevent generic success response
|
||||
}
|
||||
}
|
||||
@@ -1697,7 +1690,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
cJSON_Delete(command_array);
|
||||
|
||||
if (result != 0) {
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
strncpy(error_message, error_msg, error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
350
src/main.c
350
src/main.c
@@ -24,6 +24,7 @@
|
||||
#include "sql_schema.h" // Embedded database schema
|
||||
#include "websockets.h" // WebSocket protocol implementation
|
||||
#include "subscriptions.h" // Subscription management system
|
||||
#include "debug.h" // Debug system
|
||||
|
||||
// Forward declarations for unified request validator
|
||||
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
@@ -95,11 +96,7 @@ extern subscription_manager_t g_subscription_manager;
|
||||
|
||||
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
// Forward declarations for logging functions - REMOVED (replaced by debug system)
|
||||
|
||||
// Forward declaration for subscription manager configuration
|
||||
void update_subscription_manager_config(void);
|
||||
@@ -189,41 +186,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Helper function to get current timestamp string
|
||||
static void get_timestamp_string(char* buffer, size_t buffer_size) {
|
||||
time_t now = time(NULL);
|
||||
struct tm* local_time = localtime(&now);
|
||||
strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", local_time);
|
||||
}
|
||||
|
||||
// Logging functions
|
||||
void log_info(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] [INFO] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_success(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] [SUCCESS] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_error(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] [ERROR] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_warning(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] [WARNING] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
// Logging functions - REMOVED (replaced by debug system in debug.c)
|
||||
|
||||
// Update subscription manager configuration from config system
|
||||
void update_subscription_manager_config(void) {
|
||||
@@ -300,27 +263,27 @@ static void cleanup_stale_wal_files(const char* db_path) {
|
||||
int has_shm = (access(shm_path, F_OK) == 0);
|
||||
|
||||
if (has_wal || has_shm) {
|
||||
log_warning("Detected stale SQLite WAL files from previous unclean shutdown");
|
||||
DEBUG_WARN("Detected stale SQLite WAL files from previous unclean shutdown");
|
||||
|
||||
// Try to remove WAL file
|
||||
if (has_wal) {
|
||||
if (unlink(wal_path) == 0) {
|
||||
log_info("Removed stale WAL file");
|
||||
DEBUG_INFO("Removed stale WAL file");
|
||||
} else {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to remove WAL file: %s", strerror(errno));
|
||||
log_warning(error_msg);
|
||||
DEBUG_WARN(error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to remove SHM file
|
||||
if (has_shm) {
|
||||
if (unlink(shm_path) == 0) {
|
||||
log_info("Removed stale SHM file");
|
||||
DEBUG_INFO("Removed stale SHM file");
|
||||
} else {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to remove SHM file: %s", strerror(errno));
|
||||
log_warning(error_msg);
|
||||
DEBUG_WARN(error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,28 +291,49 @@ static void cleanup_stale_wal_files(const char* db_path) {
|
||||
|
||||
// Initialize database connection and schema
|
||||
int init_database(const char* database_path_override) {
|
||||
DEBUG_TRACE("Entering init_database()");
|
||||
|
||||
// Priority 1: Command line database path override
|
||||
const char* db_path = database_path_override;
|
||||
|
||||
|
||||
// Priority 2: Configuration system (if available)
|
||||
if (!db_path) {
|
||||
db_path = get_config_value("database_path");
|
||||
}
|
||||
|
||||
|
||||
// Priority 3: Default path
|
||||
if (!db_path) {
|
||||
db_path = DEFAULT_DATABASE_PATH;
|
||||
}
|
||||
|
||||
|
||||
DEBUG_LOG("Initializing database: %s", db_path);
|
||||
|
||||
// Clean up stale WAL files before opening database
|
||||
cleanup_stale_wal_files(db_path);
|
||||
|
||||
|
||||
int rc = sqlite3_open(db_path, &g_db);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Cannot open database");
|
||||
DEBUG_ERROR("Cannot open database");
|
||||
DEBUG_TRACE("Exiting init_database() - failed to open database");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
// Check config table row count immediately after database open
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count immediately after sqlite3_open(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
DEBUG_LOG("Config table does not exist yet (first-time startup)");
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Check if database is already initialized by looking for the events table
|
||||
const char* check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='events'";
|
||||
sqlite3_stmt* check_stmt;
|
||||
@@ -379,7 +363,7 @@ int init_database(const char* database_path_override) {
|
||||
} else {
|
||||
char warning_msg[256];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version);
|
||||
log_warning(warning_msg);
|
||||
DEBUG_WARN(warning_msg);
|
||||
}
|
||||
} else {
|
||||
needs_migration = 1;
|
||||
@@ -422,7 +406,7 @@ int init_database(const char* database_path_override) {
|
||||
char error_log[512];
|
||||
snprintf(error_log, sizeof(error_log), "Failed to create auth_rules table: %s",
|
||||
error_msg ? error_msg : "unknown error");
|
||||
log_error(error_log);
|
||||
DEBUG_ERROR(error_log);
|
||||
if (error_msg) sqlite3_free(error_msg);
|
||||
return -1;
|
||||
}
|
||||
@@ -439,7 +423,7 @@ int init_database(const char* database_path_override) {
|
||||
char index_error_log[512];
|
||||
snprintf(index_error_log, sizeof(index_error_log), "Failed to create auth_rules indexes: %s",
|
||||
index_error_msg ? index_error_msg : "unknown error");
|
||||
log_error(index_error_log);
|
||||
DEBUG_ERROR(index_error_log);
|
||||
if (index_error_msg) sqlite3_free(index_error_msg);
|
||||
return -1;
|
||||
}
|
||||
@@ -458,7 +442,7 @@ int init_database(const char* database_path_override) {
|
||||
char error_log[512];
|
||||
snprintf(error_log, sizeof(error_log), "Failed to update schema version: %s",
|
||||
error_msg ? error_msg : "unknown error");
|
||||
log_error(error_log);
|
||||
DEBUG_ERROR(error_log);
|
||||
if (error_msg) sqlite3_free(error_msg);
|
||||
return -1;
|
||||
}
|
||||
@@ -474,7 +458,7 @@ int init_database(const char* database_path_override) {
|
||||
char error_log[512];
|
||||
snprintf(error_log, sizeof(error_log), "Failed to initialize database schema: %s",
|
||||
error_msg ? error_msg : "unknown error");
|
||||
log_error(error_log);
|
||||
DEBUG_ERROR(error_log);
|
||||
if (error_msg) {
|
||||
sqlite3_free(error_msg);
|
||||
}
|
||||
@@ -483,7 +467,7 @@ int init_database(const char* database_path_override) {
|
||||
|
||||
}
|
||||
} else {
|
||||
log_error("Failed to check existing database schema");
|
||||
DEBUG_ERROR("Failed to check existing database schema");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -493,36 +477,41 @@ int init_database(const char* database_path_override) {
|
||||
if (rc != SQLITE_OK) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to enable WAL mode: %s",
|
||||
wal_error ? wal_error : "unknown error");
|
||||
log_warning(error_msg);
|
||||
wal_error ? wal_error : "unknown error");
|
||||
DEBUG_WARN(error_msg);
|
||||
if (wal_error) sqlite3_free(wal_error);
|
||||
// Continue anyway - WAL mode is optional
|
||||
} else {
|
||||
log_info("SQLite WAL mode enabled");
|
||||
DEBUG_LOG("SQLite WAL mode enabled");
|
||||
}
|
||||
|
||||
|
||||
DEBUG_TRACE("Exiting init_database() - success");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Close database connection with proper WAL checkpoint
|
||||
void close_database() {
|
||||
DEBUG_TRACE("Entering close_database()");
|
||||
|
||||
if (g_db) {
|
||||
// Perform WAL checkpoint to minimize stale files on next startup
|
||||
log_info("Performing WAL checkpoint before database close");
|
||||
DEBUG_LOG("Performing WAL checkpoint before database close");
|
||||
char* checkpoint_error = NULL;
|
||||
int rc = sqlite3_exec(g_db, "PRAGMA wal_checkpoint(TRUNCATE);", NULL, NULL, &checkpoint_error);
|
||||
if (rc != SQLITE_OK) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "WAL checkpoint warning: %s",
|
||||
checkpoint_error ? checkpoint_error : "unknown error");
|
||||
log_warning(error_msg);
|
||||
checkpoint_error ? checkpoint_error : "unknown error");
|
||||
DEBUG_WARN(error_msg);
|
||||
if (checkpoint_error) sqlite3_free(checkpoint_error);
|
||||
}
|
||||
|
||||
|
||||
sqlite3_close(g_db);
|
||||
g_db = NULL;
|
||||
log_info("Database connection closed");
|
||||
DEBUG_LOG("Database connection closed");
|
||||
}
|
||||
|
||||
DEBUG_TRACE("Exiting close_database()");
|
||||
}
|
||||
|
||||
// Event type classification
|
||||
@@ -691,7 +680,7 @@ int store_event(cJSON* event) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
|
||||
if (!id || !pubkey || !created_at || !kind || !content || !sig) {
|
||||
log_error("Invalid event - missing required fields");
|
||||
DEBUG_ERROR("Invalid event - missing required fields");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -707,7 +696,7 @@ int store_event(cJSON* event) {
|
||||
}
|
||||
|
||||
if (!tags_json) {
|
||||
log_error("Failed to serialize tags to JSON");
|
||||
DEBUG_ERROR("Failed to serialize tags to JSON");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -719,7 +708,7 @@ int store_event(cJSON* event) {
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Failed to prepare event insert statement");
|
||||
DEBUG_ERROR("Failed to prepare event insert statement");
|
||||
free(tags_json);
|
||||
return -1;
|
||||
}
|
||||
@@ -740,13 +729,13 @@ int store_event(cJSON* event) {
|
||||
|
||||
if (rc != SQLITE_DONE) {
|
||||
if (rc == SQLITE_CONSTRAINT) {
|
||||
log_warning("Event already exists in database");
|
||||
DEBUG_WARN("Event already exists in database");
|
||||
free(tags_json);
|
||||
return 0; // Not an error, just duplicate
|
||||
}
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", sqlite3_errmsg(g_db));
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
free(tags_json);
|
||||
return -1;
|
||||
}
|
||||
@@ -815,7 +804,7 @@ cJSON* retrieve_event(const char* event_id) {
|
||||
|
||||
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
|
||||
if (!cJSON_IsArray(filters)) {
|
||||
log_error("REQ filters is not an array");
|
||||
DEBUG_ERROR("REQ filters is not an array");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -859,7 +848,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
|
||||
// Check session subscription limits
|
||||
if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
|
||||
log_error("Maximum subscriptions per client exceeded");
|
||||
DEBUG_ERROR("Maximum subscriptions per client exceeded");
|
||||
|
||||
// Update rate limiting counters for failed attempt
|
||||
pss->failed_subscription_attempts++;
|
||||
@@ -953,13 +942,13 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
// Create persistent subscription
|
||||
subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
|
||||
if (!subscription) {
|
||||
log_error("Failed to create subscription");
|
||||
DEBUG_ERROR("Failed to create subscription");
|
||||
return has_config_request ? config_events_sent : 0;
|
||||
}
|
||||
|
||||
// Add to global manager
|
||||
if (add_subscription_to_manager(subscription) != 0) {
|
||||
log_error("Failed to add subscription to global manager");
|
||||
DEBUG_ERROR("Failed to add subscription to global manager");
|
||||
free_subscription(subscription);
|
||||
|
||||
// Send CLOSED notice
|
||||
@@ -1007,7 +996,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
||||
cJSON* filter = cJSON_GetArrayItem(filters, i);
|
||||
if (!filter || !cJSON_IsObject(filter)) {
|
||||
log_warning("Invalid filter object");
|
||||
DEBUG_WARN("Invalid filter object");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1250,7 +1239,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
if (rc != SQLITE_OK) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to prepare subscription query: %s", sqlite3_errmsg(g_db));
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1395,7 +1384,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
||||
// Step 3: Verify admin signature authorization
|
||||
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
|
||||
log_warning("Unauthorized admin event attempt: missing or invalid pubkey");
|
||||
DEBUG_WARN("Unauthorized admin event attempt: missing or invalid pubkey");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: missing pubkey");
|
||||
return -1;
|
||||
}
|
||||
@@ -1403,19 +1392,19 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
||||
// Get admin pubkey from configuration
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!admin_pubkey || strlen(admin_pubkey) == 0) {
|
||||
log_warning("Unauthorized admin event attempt: no admin pubkey configured");
|
||||
DEBUG_WARN("Unauthorized admin event attempt: no admin pubkey configured");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: no admin configured");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Compare pubkeys
|
||||
if (strcmp(pubkey_json->valuestring, admin_pubkey) != 0) {
|
||||
log_warning("Unauthorized admin event attempt: pubkey mismatch");
|
||||
DEBUG_WARN("Unauthorized admin event attempt: pubkey mismatch");
|
||||
char warning_msg[256];
|
||||
snprintf(warning_msg, sizeof(warning_msg),
|
||||
"Unauthorized admin event attempt from pubkey: %.32s...", pubkey_json->valuestring);
|
||||
log_warning(warning_msg);
|
||||
log_info("DEBUG: Pubkey comparison failed - event pubkey != admin pubkey");
|
||||
DEBUG_WARN(warning_msg);
|
||||
DEBUG_INFO("DEBUG: Pubkey comparison failed - event pubkey != admin pubkey");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: invalid admin pubkey");
|
||||
return -1;
|
||||
}
|
||||
@@ -1423,7 +1412,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
||||
|
||||
// Step 4: Verify event signature
|
||||
if (nostr_verify_event_signature(event) != 0) {
|
||||
log_warning("Unauthorized admin event attempt: invalid signature");
|
||||
DEBUG_WARN("Unauthorized admin event attempt: invalid signature");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: signature verification failed");
|
||||
return -1;
|
||||
}
|
||||
@@ -1455,6 +1444,8 @@ void print_usage(const char* program_name) {
|
||||
printf(" --strict-port Fail if exact port is unavailable (no port increment)\n");
|
||||
printf(" -a, --admin-pubkey KEY Override admin public key (64-char hex or npub)\n");
|
||||
printf(" -r, --relay-privkey KEY Override relay private key (64-char hex or nsec)\n");
|
||||
printf(" --debug-level=N Set debug output level (0-5, default: 0)\n");
|
||||
printf(" 0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace\n");
|
||||
printf("\n");
|
||||
printf("Configuration:\n");
|
||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||
@@ -1492,7 +1483,8 @@ int main(int argc, char* argv[]) {
|
||||
.port_override = -1, // -1 = not set
|
||||
.admin_pubkey_override = {0}, // Empty string = not set
|
||||
.relay_privkey_override = {0}, // Empty string = not set
|
||||
.strict_port = 0 // 0 = allow port increment (default)
|
||||
.strict_port = 0, // 0 = allow port increment (default)
|
||||
.debug_level = 0 // 0 = no debug output (default)
|
||||
};
|
||||
|
||||
// Parse command line arguments
|
||||
@@ -1506,7 +1498,7 @@ int main(int argc, char* argv[]) {
|
||||
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
|
||||
// Port override option
|
||||
if (i + 1 >= argc) {
|
||||
log_error("Port option requires a value. Use --help for usage information.");
|
||||
DEBUG_ERROR("Port option requires a value. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1516,7 +1508,7 @@ int main(int argc, char* argv[]) {
|
||||
long port = strtol(argv[i + 1], &endptr, 10);
|
||||
|
||||
if (endptr == argv[i + 1] || *endptr != '\0' || port < 1 || port > 65535) {
|
||||
log_error("Invalid port number. Port must be between 1 and 65535.");
|
||||
DEBUG_ERROR("Invalid port number. Port must be between 1 and 65535.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1529,7 +1521,7 @@ int main(int argc, char* argv[]) {
|
||||
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
|
||||
// Admin public key override option
|
||||
if (i + 1 >= argc) {
|
||||
log_error("Admin pubkey option requires a value. Use --help for usage information.");
|
||||
DEBUG_ERROR("Admin pubkey option requires a value. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1547,7 +1539,7 @@ int main(int argc, char* argv[]) {
|
||||
hex_ptr += 2;
|
||||
}
|
||||
} else {
|
||||
log_error("Invalid admin public key format. Must be 64 hex characters or valid npub format.");
|
||||
DEBUG_ERROR("Invalid admin public key format. Must be 64 hex characters or valid npub format.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1559,7 +1551,7 @@ int main(int argc, char* argv[]) {
|
||||
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
|
||||
// Relay private key override option
|
||||
if (i + 1 >= argc) {
|
||||
log_error("Relay privkey option requires a value. Use --help for usage information.");
|
||||
DEBUG_ERROR("Relay privkey option requires a value. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1577,7 +1569,7 @@ int main(int argc, char* argv[]) {
|
||||
hex_ptr += 2;
|
||||
}
|
||||
} else {
|
||||
log_error("Invalid relay private key format. Must be 64 hex characters or valid nsec format.");
|
||||
DEBUG_ERROR("Invalid relay private key format. Must be 64 hex characters or valid nsec format.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1589,13 +1581,28 @@ int main(int argc, char* argv[]) {
|
||||
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
||||
// Strict port mode option
|
||||
cli_options.strict_port = 1;
|
||||
} else if (strncmp(argv[i], "--debug-level=", 14) == 0) {
|
||||
// Debug level option
|
||||
char* endptr;
|
||||
int debug_level = (int)strtol(argv[i] + 14, &endptr, 10);
|
||||
|
||||
if (endptr == argv[i] + 14 || *endptr != '\0' || debug_level < 0 || debug_level > 5) {
|
||||
DEBUG_ERROR("Invalid debug level. Debug level must be between 0 and 5.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cli_options.debug_level = debug_level;
|
||||
} else {
|
||||
log_error("Unknown argument. Use --help for usage information.");
|
||||
DEBUG_ERROR("Unknown argument. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize debug system
|
||||
debug_init(cli_options.debug_level);
|
||||
|
||||
// Set up signal handlers
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
@@ -1603,72 +1610,108 @@ int main(int argc, char* argv[]) {
|
||||
printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n");
|
||||
|
||||
|
||||
DEBUG_TRACE("Starting main initialization sequence");
|
||||
|
||||
// Initialize nostr library FIRST (required for key generation and event creation)
|
||||
if (nostr_init() != 0) {
|
||||
log_error("Failed to initialize nostr library");
|
||||
DEBUG_ERROR("Failed to initialize nostr library");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
DEBUG_LOG("Nostr library initialized");
|
||||
|
||||
// Check if this is first-time startup or existing relay
|
||||
if (is_first_time_startup()) {
|
||||
DEBUG_LOG("First-time startup detected");
|
||||
|
||||
// Initialize event-based configuration system
|
||||
if (init_configuration_system(NULL, NULL) != 0) {
|
||||
log_error("Failed to initialize event-based configuration system");
|
||||
DEBUG_ERROR("Failed to initialize event-based configuration system");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet)
|
||||
if (first_time_startup_sequence(&cli_options) != 0) {
|
||||
log_error("Failed to complete first-time startup sequence");
|
||||
DEBUG_ERROR("Failed to complete first-time startup sequence");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize database with the generated relay pubkey
|
||||
DEBUG_TRACE("Initializing database for first-time startup");
|
||||
if (init_database(g_database_path) != 0) {
|
||||
log_error("Failed to initialize database after first-time setup");
|
||||
DEBUG_ERROR("Failed to initialize database after first-time setup");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
DEBUG_LOG("Database initialized for first-time startup");
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after init_database() (first-time): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Now that database is available, store the relay private key securely
|
||||
const char* relay_privkey = get_temp_relay_private_key();
|
||||
if (relay_privkey) {
|
||||
if (store_relay_private_key(relay_privkey) != 0) {
|
||||
log_error("Failed to store relay private key securely after database initialization");
|
||||
DEBUG_ERROR("Failed to store relay private key securely after database initialization");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
log_error("Relay private key not available from first-time startup");
|
||||
DEBUG_ERROR("Relay private key not available from first-time startup");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// COMMENTED OUT: Old incremental config building code replaced by unified startup sequence
|
||||
// The new first_time_startup_sequence() function handles all config creation atomically
|
||||
/*
|
||||
// Handle configuration setup after database is initialized
|
||||
// Always populate defaults directly in config table (abandoning legacy event signing)
|
||||
|
||||
// Populate default config values in table
|
||||
if (populate_default_config_values() != 0) {
|
||||
log_error("Failed to populate default config values");
|
||||
DEBUG_ERROR("Failed to populate default config values");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after populate_default_config_values(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Apply CLI overrides now that database is available
|
||||
if (cli_options.port_override > 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
|
||||
if (update_config_in_table("relay_port", port_str) != 0) {
|
||||
log_error("Failed to update relay port override in config table");
|
||||
DEBUG_ERROR("Failed to update relay port override in config table");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
@@ -1679,17 +1722,31 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Add pubkeys to config table (single authoritative call)
|
||||
if (add_pubkeys_to_config_table() != 0) {
|
||||
log_error("Failed to add pubkeys to config table");
|
||||
DEBUG_ERROR("Failed to add pubkeys to config table");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after add_pubkeys_to_config_table() (first-time): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
*/
|
||||
} else {
|
||||
// Find existing database file
|
||||
char** existing_files = find_existing_db_files();
|
||||
if (!existing_files || !existing_files[0]) {
|
||||
log_error("No existing relay database found");
|
||||
DEBUG_ERROR("No existing relay database found");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
@@ -1697,7 +1754,7 @@ int main(int argc, char* argv[]) {
|
||||
// Extract relay pubkey from filename
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
if (!relay_pubkey) {
|
||||
log_error("Failed to extract relay pubkey from database filename");
|
||||
DEBUG_ERROR("Failed to extract relay pubkey from database filename");
|
||||
// Free the files array
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
@@ -1709,7 +1766,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Initialize event-based configuration system
|
||||
if (init_configuration_system(NULL, NULL) != 0) {
|
||||
log_error("Failed to initialize event-based configuration system");
|
||||
DEBUG_ERROR("Failed to initialize event-based configuration system");
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
@@ -1720,8 +1777,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// Setup existing relay (sets database path and loads config)
|
||||
if (startup_existing_relay(relay_pubkey) != 0) {
|
||||
log_error("Failed to setup existing relay");
|
||||
if (startup_existing_relay(relay_pubkey, &cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to setup existing relay");
|
||||
cleanup_configuration_system();
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
@@ -1739,7 +1796,8 @@ int main(int argc, char* argv[]) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(temp_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
// Row count check completed
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
printf(" Config table row count before database initialization: %d\n", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
@@ -1748,8 +1806,9 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// Initialize database with existing database path
|
||||
DEBUG_TRACE("Initializing existing database");
|
||||
if (init_database(g_database_path) != 0) {
|
||||
log_error("Failed to initialize existing database");
|
||||
DEBUG_ERROR("Failed to initialize existing database");
|
||||
cleanup_configuration_system();
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
@@ -1759,75 +1818,107 @@ int main(int argc, char* argv[]) {
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
DEBUG_LOG("Existing database initialized");
|
||||
|
||||
// Check config table row count after database initialization
|
||||
{
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
// Row count check completed
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after init_database(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// COMMENTED OUT: Old incremental config building code replaced by unified startup sequence
|
||||
// The new startup_existing_relay() function handles all config loading atomically
|
||||
/*
|
||||
// Ensure default configuration values are populated (for any missing keys)
|
||||
// This must be done AFTER database initialization
|
||||
// COMMENTED OUT: Don't modify existing database config on restart
|
||||
// if (populate_default_config_values() != 0) {
|
||||
// log_warning("Failed to populate default config values for existing relay - continuing");
|
||||
// DEBUG_WARN("Failed to populate default config values for existing relay - continuing");
|
||||
// }
|
||||
|
||||
|
||||
// Load configuration from database
|
||||
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
||||
if (config_event) {
|
||||
if (apply_configuration_from_event(config_event) != 0) {
|
||||
log_warning("Failed to apply configuration from database");
|
||||
DEBUG_WARN("Failed to apply configuration from database");
|
||||
}
|
||||
cJSON_Delete(config_event);
|
||||
} else {
|
||||
// This is expected for relays using table-based configuration
|
||||
// No longer a warning - just informational
|
||||
}
|
||||
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count before checking pubkeys: %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Ensure pubkeys are in config table for existing relay
|
||||
// This handles migration from old event-based config to table-based config
|
||||
const char* admin_pubkey_from_table = get_config_value_from_table("admin_pubkey");
|
||||
const char* relay_pubkey_from_table = get_config_value_from_table("relay_pubkey");
|
||||
|
||||
|
||||
int need_to_add_pubkeys = 0;
|
||||
|
||||
|
||||
// Check if admin_pubkey is missing or invalid
|
||||
if (!admin_pubkey_from_table || strlen(admin_pubkey_from_table) != 64) {
|
||||
log_warning("Admin pubkey missing or invalid in config table - will regenerate from cache");
|
||||
DEBUG_WARN("Admin pubkey missing or invalid in config table - will regenerate from cache");
|
||||
need_to_add_pubkeys = 1;
|
||||
}
|
||||
if (admin_pubkey_from_table) free((char*)admin_pubkey_from_table);
|
||||
|
||||
|
||||
// Check if relay_pubkey is missing or invalid
|
||||
if (!relay_pubkey_from_table || strlen(relay_pubkey_from_table) != 64) {
|
||||
log_warning("Relay pubkey missing or invalid in config table - will regenerate from cache");
|
||||
DEBUG_WARN("Relay pubkey missing or invalid in config table - will regenerate from cache");
|
||||
need_to_add_pubkeys = 1;
|
||||
}
|
||||
if (relay_pubkey_from_table) free((char*)relay_pubkey_from_table);
|
||||
|
||||
|
||||
// If either pubkey is missing, call add_pubkeys_to_config_table to populate both
|
||||
if (need_to_add_pubkeys) {
|
||||
if (add_pubkeys_to_config_table() != 0) {
|
||||
log_error("Failed to add pubkeys to config table for existing relay");
|
||||
DEBUG_ERROR("Failed to add pubkeys to config table for existing relay");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after add_pubkeys_to_config_table(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
}
|
||||
|
||||
|
||||
// Apply CLI overrides for existing relay (port override should work even for existing relays)
|
||||
if (cli_options.port_override > 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
|
||||
if (update_config_in_table("relay_port", port_str) != 0) {
|
||||
log_error("Failed to update relay port override in config table for existing relay");
|
||||
DEBUG_ERROR("Failed to update relay port override in config table for existing relay");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
@@ -1835,6 +1926,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
||||
}
|
||||
*/
|
||||
|
||||
// Free memory
|
||||
free(relay_pubkey);
|
||||
@@ -1846,7 +1938,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Verify database is now available
|
||||
if (!g_db) {
|
||||
log_error("Database not available after initialization");
|
||||
DEBUG_ERROR("Database not available after initialization");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
@@ -1857,7 +1949,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Initialize unified request validator system
|
||||
if (ginxsom_request_validator_init(g_database_path, "c-relay") != 0) {
|
||||
log_error("Failed to initialize unified request validator");
|
||||
DEBUG_ERROR("Failed to initialize unified request validator");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
@@ -1877,7 +1969,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Initialize subscription manager mutexes
|
||||
if (pthread_mutex_init(&g_subscription_manager.subscriptions_lock, NULL) != 0) {
|
||||
log_error("Failed to initialize subscription manager subscriptions lock");
|
||||
DEBUG_ERROR("Failed to initialize subscription manager subscriptions lock");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
@@ -1885,7 +1977,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&g_subscription_manager.ip_tracking_lock, NULL) != 0) {
|
||||
log_error("Failed to initialize subscription manager IP tracking lock");
|
||||
DEBUG_ERROR("Failed to initialize subscription manager IP tracking lock");
|
||||
pthread_mutex_destroy(&g_subscription_manager.subscriptions_lock);
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
@@ -1912,7 +2004,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (result == 0) {
|
||||
} else {
|
||||
log_error("Server shutdown with errors");
|
||||
DEBUG_ERROR("Server shutdown with errors");
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
10
src/nip009.c
10
src/nip009.c
@@ -6,15 +6,13 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
#include <cjson/cJSON.h>
|
||||
#include "debug.h"
|
||||
#include <sqlite3.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_warning(const char* message);
|
||||
void log_info(const char* message);
|
||||
|
||||
// Forward declaration for database functions
|
||||
int store_event(cJSON* event);
|
||||
@@ -139,7 +137,7 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
|
||||
|
||||
// Store the deletion request itself (it should be kept according to NIP-09)
|
||||
if (store_event(event) != 0) {
|
||||
log_warning("Failed to store deletion request event");
|
||||
DEBUG_WARN("Failed to store deletion request event");
|
||||
}
|
||||
|
||||
error_message[0] = '\0'; // Success - empty error message
|
||||
@@ -198,7 +196,7 @@ int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
|
||||
log_warning(warning_msg);
|
||||
DEBUG_WARN(warning_msg);
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
@@ -244,7 +242,7 @@ int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, lon
|
||||
free(addr_copy);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr);
|
||||
log_warning(warning_msg);
|
||||
DEBUG_WARN(warning_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
18
src/nip011.c
18
src/nip011.c
@@ -1,6 +1,7 @@
|
||||
// NIP-11 Relay Information Document module
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include "debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
@@ -8,11 +9,6 @@
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
#include "config.h"
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Forward declarations for configuration functions
|
||||
const char* get_config_value(const char* key);
|
||||
@@ -264,7 +260,7 @@ void cleanup_relay_info() {
|
||||
cJSON* generate_relay_info_json() {
|
||||
cJSON* info = cJSON_CreateObject();
|
||||
if (!info) {
|
||||
log_error("Failed to create relay info JSON object");
|
||||
DEBUG_ERROR("Failed to create relay info JSON object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -274,7 +270,7 @@ cJSON* generate_relay_info_json() {
|
||||
if (strlen(g_unified_cache.relay_info.name) == 0 &&
|
||||
strlen(g_unified_cache.relay_info.description) == 0 &&
|
||||
strlen(g_unified_cache.relay_info.software) == 0) {
|
||||
log_warning("NIP-11 relay_info appears empty, rebuilding directly from config table");
|
||||
DEBUG_WARN("NIP-11 relay_info appears empty, rebuilding directly from config table");
|
||||
|
||||
// Rebuild relay_info directly from config table to avoid circular cache dependency
|
||||
// Get values directly from table (similar to init_relay_info but without cache calls)
|
||||
@@ -534,7 +530,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
}
|
||||
|
||||
if (!accepts_nostr_json) {
|
||||
log_warning("HTTP request without proper Accept header for NIP-11");
|
||||
DEBUG_WARN("HTTP request without proper Accept header for NIP-11");
|
||||
// Return 406 Not Acceptable
|
||||
unsigned char buf[LWS_PRE + 256];
|
||||
unsigned char *p = &buf[LWS_PRE];
|
||||
@@ -560,7 +556,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
// Generate relay information JSON
|
||||
cJSON* info_json = generate_relay_info_json();
|
||||
if (!info_json) {
|
||||
log_error("Failed to generate relay info JSON");
|
||||
DEBUG_ERROR("Failed to generate relay info JSON");
|
||||
unsigned char buf[LWS_PRE + 256];
|
||||
unsigned char *p = &buf[LWS_PRE];
|
||||
unsigned char *start = p;
|
||||
@@ -586,7 +582,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
cJSON_Delete(info_json);
|
||||
|
||||
if (!json_string) {
|
||||
log_error("Failed to serialize relay info JSON");
|
||||
DEBUG_ERROR("Failed to serialize relay info JSON");
|
||||
unsigned char buf[LWS_PRE + 256];
|
||||
unsigned char *p = &buf[LWS_PRE];
|
||||
unsigned char *start = p;
|
||||
@@ -613,7 +609,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
// Allocate session data to manage buffer lifetime across callbacks
|
||||
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
|
||||
if (!session_data) {
|
||||
log_error("Failed to allocate NIP-11 session data");
|
||||
DEBUG_ERROR("Failed to allocate NIP-11 session data");
|
||||
free(json_string);
|
||||
return -1;
|
||||
}
|
||||
|
||||
20
src/nip013.c
20
src/nip013.c
@@ -1,5 +1,6 @@
|
||||
// NIP-13 Proof of Work validation module
|
||||
#include <stdio.h>
|
||||
#include "debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
@@ -8,11 +9,6 @@
|
||||
#include "../nostr_core_lib/nostr_core/nip013.h"
|
||||
#include "config.h"
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// NIP-13 PoW configuration structure
|
||||
struct pow_config {
|
||||
@@ -121,39 +117,39 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
snprintf(error_message, error_size,
|
||||
"pow: insufficient difficulty: %d < %d",
|
||||
pow_result.actual_difficulty, min_pow_difficulty);
|
||||
log_warning("Event rejected: insufficient PoW difficulty");
|
||||
DEBUG_WARN("Event rejected: insufficient PoW difficulty");
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
|
||||
// This should not happen with min_difficulty=0 after our check above
|
||||
if (min_pow_difficulty > 0) {
|
||||
snprintf(error_message, error_size, "pow: missing required nonce tag");
|
||||
log_warning("Event rejected: missing nonce tag");
|
||||
DEBUG_WARN("Event rejected: missing nonce tag");
|
||||
} else {
|
||||
return 0; // Allow when min_difficulty=0
|
||||
}
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG:
|
||||
snprintf(error_message, error_size, "pow: invalid nonce tag format");
|
||||
log_warning("Event rejected: invalid nonce tag format");
|
||||
DEBUG_WARN("Event rejected: invalid nonce tag format");
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
|
||||
snprintf(error_message, error_size,
|
||||
"pow: committed target (%d) lower than minimum (%d)",
|
||||
pow_result.committed_target, min_pow_difficulty);
|
||||
log_warning("Event rejected: committed target too low (anti-spam protection)");
|
||||
DEBUG_WARN("Event rejected: committed target too low (anti-spam protection)");
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_CALCULATION:
|
||||
snprintf(error_message, error_size, "pow: difficulty calculation failed");
|
||||
log_error("PoW difficulty calculation error");
|
||||
DEBUG_ERROR("PoW difficulty calculation error");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_ID:
|
||||
snprintf(error_message, error_size, "pow: invalid event ID format");
|
||||
log_warning("Event rejected: invalid event ID for PoW calculation");
|
||||
DEBUG_WARN("Event rejected: invalid event ID for PoW calculation");
|
||||
break;
|
||||
default:
|
||||
snprintf(error_message, error_size, "pow: validation failed - %s",
|
||||
strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error");
|
||||
log_warning("Event rejected: PoW validation failed");
|
||||
DEBUG_WARN("Event rejected: PoW validation failed");
|
||||
}
|
||||
return validation_result;
|
||||
}
|
||||
|
||||
10
src/nip040.c
10
src/nip040.c
@@ -1,5 +1,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include "debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
@@ -28,9 +29,6 @@ struct expiration_config g_expiration_config = {
|
||||
.grace_period = 1 // 1 second grace period for testing (was 300)
|
||||
};
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Initialize expiration configuration using configuration system
|
||||
void init_expiration_config() {
|
||||
@@ -51,7 +49,7 @@ void init_expiration_config() {
|
||||
|
||||
// Validate grace period bounds
|
||||
if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
|
||||
log_warning("Invalid grace period, using default of 300 seconds");
|
||||
DEBUG_WARN("Invalid grace period, using default of 300 seconds");
|
||||
g_expiration_config.grace_period = 300;
|
||||
}
|
||||
|
||||
@@ -94,7 +92,7 @@ long extract_expiration_timestamp(cJSON* tags) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Ignoring malformed expiration tag value: '%.32s'", value);
|
||||
log_warning(debug_msg);
|
||||
DEBUG_WARN(debug_msg);
|
||||
continue; // Ignore malformed expiration tag
|
||||
}
|
||||
|
||||
@@ -148,7 +146,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
snprintf(error_message, error_size,
|
||||
"invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
|
||||
expiration_ts, (long)current_time, g_expiration_config.grace_period);
|
||||
log_warning("Event rejected: expired timestamp");
|
||||
DEBUG_WARN("Event rejected: expired timestamp");
|
||||
return -1;
|
||||
} else {
|
||||
// In non-strict mode, allow expired events
|
||||
|
||||
14
src/nip042.c
14
src/nip042.c
@@ -6,17 +6,13 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
#include <pthread.h>
|
||||
#include "debug.h"
|
||||
#include <cjson/cJSON.h>
|
||||
#include <libwebsockets.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_error(const char* message);
|
||||
void log_info(const char* message);
|
||||
void log_warning(const char* message);
|
||||
void log_success(const char* message);
|
||||
|
||||
// Forward declaration for notice message function
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
@@ -52,7 +48,7 @@ void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss) {
|
||||
// Generate challenge using existing request_validator function
|
||||
char challenge[65];
|
||||
if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) {
|
||||
log_error("Failed to generate NIP-42 challenge");
|
||||
DEBUG_ERROR("Failed to generate NIP-42 challenge");
|
||||
send_notice_message(wsi, "Authentication temporarily unavailable");
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +104,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
||||
if (current_time > challenge_expires) {
|
||||
free(event_json);
|
||||
send_notice_message(wsi, "Authentication challenge expired, please retry");
|
||||
log_warning("NIP-42 authentication failed: challenge expired");
|
||||
DEBUG_WARN("NIP-42 authentication failed: challenge expired");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +150,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"NIP-42 authentication failed (error code: %d)", result);
|
||||
log_warning(error_msg);
|
||||
DEBUG_WARN(error_msg);
|
||||
|
||||
send_notice_message(wsi, "NIP-42 authentication failed - invalid signature or challenge");
|
||||
}
|
||||
@@ -166,6 +162,6 @@ void handle_nip42_auth_challenge_response(struct lws* wsi, struct per_session_da
|
||||
|
||||
// NIP-42 doesn't typically use challenge responses from client to server
|
||||
// This is reserved for potential future use or protocol extensions
|
||||
log_warning("Received unexpected challenge response from client (not part of standard NIP-42 flow)");
|
||||
DEBUG_WARN("Received unexpected challenge response from client (not part of standard NIP-42 flow)");
|
||||
send_notice_message(wsi, "Challenge responses are not supported - please send signed authentication event");
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ extern struct expiration_config {
|
||||
// Configuration functions from C-relay
|
||||
extern int get_config_bool(const char* key, int default_value);
|
||||
extern int get_config_int(const char* key, int default_value);
|
||||
extern const char* get_admin_pubkey_cached(void);
|
||||
|
||||
// NIP-42 constants (from nostr_core_lib)
|
||||
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242
|
||||
@@ -301,7 +300,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
// 8. Check if this is a kind 23456 admin event from authorized admin
|
||||
// This must happen AFTER signature validation but BEFORE auth rules
|
||||
if (event_kind == 23456) {
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||
// Valid admin event - bypass remaining validation
|
||||
cJSON_Delete(event);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <cjson/cJSON.h>
|
||||
#include "debug.h"
|
||||
#include <sqlite3.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -10,9 +11,6 @@
|
||||
#include "subscriptions.h"
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Forward declarations for configuration functions
|
||||
const char* get_config_value(const char* key);
|
||||
@@ -52,7 +50,7 @@ subscription_filter_t* create_subscription_filter(cJSON* filter_json) {
|
||||
// Validate filter values before creating the filter
|
||||
char error_message[512] = {0};
|
||||
if (!validate_filter_values(filter_json, error_message, sizeof(error_message))) {
|
||||
log_warning(error_message);
|
||||
DEBUG_WARN(error_message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -150,19 +148,19 @@ static int validate_subscription_id(const char* sub_id) {
|
||||
// Create a new subscription
|
||||
subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON* filters_array, const char* client_ip) {
|
||||
if (!sub_id || !wsi || !filters_array) {
|
||||
log_error("create_subscription: NULL parameter(s)");
|
||||
DEBUG_ERROR("create_subscription: NULL parameter(s)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Validate subscription ID
|
||||
if (!validate_subscription_id(sub_id)) {
|
||||
log_error("create_subscription: invalid subscription ID format or length");
|
||||
DEBUG_ERROR("create_subscription: invalid subscription ID format or length");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
subscription_t* sub = calloc(1, sizeof(subscription_t));
|
||||
if (!sub) {
|
||||
log_error("create_subscription: failed to allocate subscription");
|
||||
DEBUG_ERROR("create_subscription: failed to allocate subscription");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -199,7 +197,7 @@ subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON*
|
||||
cJSON* filter_json = NULL;
|
||||
cJSON_ArrayForEach(filter_json, filters_array) {
|
||||
if (filter_count >= MAX_FILTERS_PER_SUBSCRIPTION) {
|
||||
log_warning("Maximum filters per subscription exceeded, ignoring excess filters");
|
||||
DEBUG_WARN("Maximum filters per subscription exceeded, ignoring excess filters");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -218,7 +216,7 @@ subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON*
|
||||
}
|
||||
|
||||
if (filter_count == 0) {
|
||||
log_error("No valid filters found for subscription");
|
||||
DEBUG_ERROR("No valid filters found for subscription");
|
||||
free(sub);
|
||||
return NULL;
|
||||
}
|
||||
@@ -246,7 +244,7 @@ int add_subscription_to_manager(subscription_t* sub) {
|
||||
// Check global limits
|
||||
if (g_subscription_manager.total_subscriptions >= g_subscription_manager.max_total_subscriptions) {
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
log_error("Maximum total subscriptions reached");
|
||||
DEBUG_ERROR("Maximum total subscriptions reached");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -267,13 +265,13 @@ int add_subscription_to_manager(subscription_t* sub) {
|
||||
// Remove subscription from global manager (thread-safe)
|
||||
int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
|
||||
if (!sub_id) {
|
||||
log_error("remove_subscription_from_manager: NULL subscription ID");
|
||||
DEBUG_ERROR("remove_subscription_from_manager: NULL subscription ID");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate subscription ID format
|
||||
if (!validate_subscription_id(sub_id)) {
|
||||
log_error("remove_subscription_from_manager: invalid subscription ID format");
|
||||
DEBUG_ERROR("remove_subscription_from_manager: invalid subscription ID format");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -319,14 +317,17 @@ int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Subscription '%s' not found for removal", sub_id);
|
||||
log_warning(debug_msg);
|
||||
DEBUG_WARN(debug_msg);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if an event matches a subscription filter
|
||||
int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
DEBUG_TRACE("Checking event against subscription filter");
|
||||
|
||||
if (!event || !filter) {
|
||||
DEBUG_TRACE("Exiting event_matches_filter - null parameters");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -502,6 +503,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_TRACE("Exiting event_matches_filter - match found");
|
||||
return 1; // All filters passed
|
||||
}
|
||||
|
||||
@@ -524,7 +526,10 @@ int event_matches_subscription(cJSON* event, subscription_t* subscription) {
|
||||
|
||||
// Broadcast event to all matching subscriptions (thread-safe)
|
||||
int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
DEBUG_TRACE("Broadcasting event to subscriptions");
|
||||
|
||||
if (!event) {
|
||||
DEBUG_TRACE("Exiting broadcast_event_to_subscriptions - null event");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -584,7 +589,7 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
matching_subs = temp;
|
||||
matching_count++;
|
||||
} else {
|
||||
log_error("broadcast_event_to_subscriptions: failed to allocate temp subscription");
|
||||
DEBUG_ERROR("broadcast_event_to_subscriptions: failed to allocate temp subscription");
|
||||
}
|
||||
}
|
||||
sub = sub->next;
|
||||
@@ -655,6 +660,8 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
g_subscription_manager.total_events_broadcast += broadcasts;
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
DEBUG_LOG("Event broadcast complete: %d subscriptions matched", broadcasts);
|
||||
DEBUG_TRACE("Exiting broadcast_event_to_subscriptions");
|
||||
return broadcasts;
|
||||
}
|
||||
|
||||
|
||||
159
src/websockets.c
159
src/websockets.c
@@ -3,6 +3,7 @@
|
||||
|
||||
// Includes
|
||||
#include <stdio.h>
|
||||
#include "debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@@ -31,10 +32,6 @@
|
||||
#include "dm_admin.h" // DM admin functions including NIP-17
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Forward declarations for configuration functions
|
||||
const char* get_config_value(const char* key);
|
||||
@@ -201,7 +198,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Allocate buffer for JSON body transmission (no LWS_PRE needed for body)
|
||||
unsigned char *json_buf = malloc(session_data->json_length);
|
||||
if (!json_buf) {
|
||||
log_error("Failed to allocate buffer for NIP-11 body transmission");
|
||||
DEBUG_ERROR("Failed to allocate buffer for NIP-11 body transmission");
|
||||
// Clean up session data
|
||||
free(session_data->json_buffer);
|
||||
free(session_data);
|
||||
@@ -219,7 +216,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
free(json_buf);
|
||||
|
||||
if (write_result < 0) {
|
||||
log_error("Failed to write NIP-11 JSON body");
|
||||
DEBUG_ERROR("Failed to write NIP-11 JSON body");
|
||||
// Clean up session data
|
||||
free(session_data->json_buffer);
|
||||
free(session_data);
|
||||
@@ -244,20 +241,23 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
DEBUG_TRACE("WebSocket connection established");
|
||||
memset(pss, 0, sizeof(*pss));
|
||||
pthread_mutex_init(&pss->session_lock, NULL);
|
||||
|
||||
|
||||
// Get real client IP address
|
||||
char client_ip[CLIENT_IP_MAX_LENGTH];
|
||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
||||
|
||||
|
||||
// Ensure client_ip is null-terminated and copy safely
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
size_t ip_len = strlen(client_ip);
|
||||
size_t copy_len = (ip_len < CLIENT_IP_MAX_LENGTH - 1) ? ip_len : CLIENT_IP_MAX_LENGTH - 1;
|
||||
memcpy(pss->client_ip, client_ip, copy_len);
|
||||
pss->client_ip[copy_len] = '\0';
|
||||
|
||||
|
||||
DEBUG_LOG("WebSocket connection established from %s", pss->client_ip);
|
||||
|
||||
// Initialize NIP-42 authentication state
|
||||
pss->authenticated = 0;
|
||||
pss->nip42_auth_required_events = get_config_bool("nip42_auth_required_events", 0);
|
||||
@@ -267,6 +267,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
memset(pss->active_challenge, 0, sizeof(pss->active_challenge));
|
||||
pss->challenge_created = 0;
|
||||
pss->challenge_expires = 0;
|
||||
DEBUG_TRACE("WebSocket connection initialization complete");
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
@@ -310,7 +311,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
if (event_kind == 14 && event_obj && cJSON_IsObject(event_obj)) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
@@ -332,11 +333,11 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
|
||||
// Special case: allow kind 23456 admin events from authorized admin to bypass auth
|
||||
if (event_kind == 23456 && event_pubkey) {
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||
bypass_auth = 1;
|
||||
} else {
|
||||
log_info("DEBUG: Kind 23456 event but pubkey mismatch or no admin pubkey");
|
||||
DEBUG_INFO("DEBUG: Kind 23456 event but pubkey mismatch or no admin pubkey");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +354,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
"NIP-42 authentication required for event kind %d", event_kind);
|
||||
}
|
||||
send_notice_message(wsi, auth_msg);
|
||||
log_warning("Event rejected: NIP-42 authentication required for kind");
|
||||
DEBUG_WARN("Event rejected: NIP-42 authentication required for kind");
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
@@ -368,7 +369,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Extract event JSON string for unified validator
|
||||
char *event_json_str = cJSON_Print(event);
|
||||
if (!event_json_str) {
|
||||
log_error("Failed to serialize event JSON for validation");
|
||||
DEBUG_ERROR("Failed to serialize event JSON for validation");
|
||||
cJSON* error_response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateString("unknown"));
|
||||
@@ -460,7 +461,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
result = -1;
|
||||
strncpy(error_message, "blocked: protected events not supported", sizeof(error_message) - 1);
|
||||
error_message[sizeof(error_message) - 1] = '\0';
|
||||
log_warning("Protected event rejected: protected events not enabled");
|
||||
DEBUG_WARN("Protected event rejected: protected events not enabled");
|
||||
} else {
|
||||
// Protected events enabled - check authentication
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
@@ -472,7 +473,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
result = -1;
|
||||
strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1);
|
||||
error_message[sizeof(error_message) - 1] = '\0';
|
||||
log_warning("Protected event rejected: authentication required");
|
||||
DEBUG_WARN("Protected event rejected: authentication required");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,10 +485,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
DEBUG_TRACE("Processing event kind %d", event_kind);
|
||||
|
||||
// Log reception of Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
DEBUG_LOG("Admin event (kind 23456) received");
|
||||
}
|
||||
|
||||
|
||||
if (event_kind == 23456) {
|
||||
// Enhanced admin event security - check authorization first
|
||||
|
||||
@@ -496,7 +500,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
|
||||
if (auth_result != 0) {
|
||||
// Authorization failed - log and reject
|
||||
log_warning("Admin event authorization failed");
|
||||
DEBUG_WARN("Admin event authorization failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(auth_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
@@ -514,12 +518,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
char error_result_msg[512];
|
||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||
log_error(error_result_msg);
|
||||
DEBUG_ERROR(error_result_msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (admin_result != 0) {
|
||||
log_error("Failed to process admin event");
|
||||
DEBUG_ERROR("Failed to process admin event");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
@@ -539,7 +543,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Check if this is an error or if the command was already handled
|
||||
if (strlen(nip17_error) > 0) {
|
||||
// There was an actual error
|
||||
log_error("NIP-17 admin message processing failed");
|
||||
DEBUG_ERROR("NIP-17 admin message processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(nip17_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
@@ -550,7 +554,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// No error message means the command was already handled (plain text commands)
|
||||
// Store the original gift wrap event in database
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store gift wrap event in database");
|
||||
DEBUG_ERROR("Failed to store gift wrap event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||
}
|
||||
@@ -558,7 +562,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
} else {
|
||||
// Store the original gift wrap event in database (unlike kind 23456)
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store gift wrap event in database");
|
||||
DEBUG_ERROR("Failed to store gift wrap event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||
cJSON_Delete(response_event);
|
||||
@@ -577,7 +581,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
|
||||
|
||||
if (dm_result != 0) {
|
||||
log_error("DM stats command processing failed");
|
||||
DEBUG_ERROR("DM stats command processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(dm_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
@@ -587,7 +591,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
} else {
|
||||
// Store the DM event in database
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store DM event in database");
|
||||
DEBUG_ERROR("Failed to store DM event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
@@ -596,21 +600,23 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_TRACE("Storing regular event in database");
|
||||
// Regular event - store in database and broadcast
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
DEBUG_ERROR("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
DEBUG_LOG("Event stored and broadcast (kind %d)", event_kind);
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event without valid kind - try normal storage
|
||||
log_warning("Event without valid kind - trying normal storage");
|
||||
DEBUG_WARN("Event without valid kind - trying normal storage");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event without kind in database");
|
||||
DEBUG_ERROR("Failed to store event without kind in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
@@ -651,7 +657,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
send_notice_message(wsi, "NIP-42 authentication required for subscriptions");
|
||||
log_warning("REQ rejected: NIP-42 authentication required");
|
||||
DEBUG_WARN("REQ rejected: NIP-42 authentication required");
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
@@ -659,26 +665,28 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
|
||||
// Handle REQ message
|
||||
cJSON* sub_id = cJSON_GetArrayItem(json, 1);
|
||||
cJSON* sub_id = cJSON_GetArrayItem(json, 1);
|
||||
|
||||
if (sub_id && cJSON_IsString(sub_id)) {
|
||||
const char* subscription_id = cJSON_GetStringValue(sub_id);
|
||||
if (sub_id && cJSON_IsString(sub_id)) {
|
||||
const char* subscription_id = cJSON_GetStringValue(sub_id);
|
||||
|
||||
// Validate subscription ID before processing
|
||||
if (!subscription_id) {
|
||||
send_notice_message(wsi, "error: invalid subscription ID");
|
||||
log_warning("REQ rejected: NULL subscription ID");
|
||||
record_malformed_request(pss);
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_TRACE("Processing REQ message for subscription %s", subscription_id);
|
||||
|
||||
// Validate subscription ID before processing
|
||||
if (!subscription_id) {
|
||||
send_notice_message(wsi, "error: invalid subscription ID");
|
||||
DEBUG_WARN("REQ rejected: NULL subscription ID");
|
||||
record_malformed_request(pss);
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check subscription ID format and length
|
||||
size_t id_len = strlen(subscription_id);
|
||||
if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) {
|
||||
send_notice_message(wsi, "error: subscription ID too long or empty");
|
||||
log_warning("REQ rejected: invalid subscription ID length");
|
||||
DEBUG_WARN("REQ rejected: invalid subscription ID length");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
@@ -704,7 +712,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"REQ rejected: invalid character '%c' (0x%02X) at position %zu in subscription ID: '%s'",
|
||||
invalid_char, (unsigned char)invalid_char, invalid_pos, subscription_id);
|
||||
log_warning(debug_msg);
|
||||
DEBUG_WARN(debug_msg);
|
||||
send_notice_message(wsi, "error: invalid characters in subscription ID");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
@@ -715,7 +723,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
cJSON* filters = cJSON_CreateArray();
|
||||
if (!filters) {
|
||||
send_notice_message(wsi, "error: failed to process filters");
|
||||
log_error("REQ failed: could not create filters array");
|
||||
DEBUG_ERROR("REQ failed: could not create filters array");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
@@ -733,7 +741,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
char filter_error[512] = {0};
|
||||
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
|
||||
send_notice_message(wsi, filter_error);
|
||||
log_warning("REQ rejected: invalid filters");
|
||||
DEBUG_WARN("REQ rejected: invalid filters");
|
||||
record_malformed_request(pss);
|
||||
cJSON_Delete(filters);
|
||||
cJSON_Delete(json);
|
||||
@@ -746,6 +754,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Clean up the filters array we created
|
||||
cJSON_Delete(filters);
|
||||
|
||||
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
|
||||
|
||||
// Send EOSE (End of Stored Events)
|
||||
cJSON* eose_response = cJSON_CreateArray();
|
||||
if (eose_response) {
|
||||
@@ -767,7 +777,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
} else {
|
||||
send_notice_message(wsi, "error: missing or invalid subscription ID in REQ");
|
||||
log_warning("REQ rejected: missing or invalid subscription ID");
|
||||
DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
|
||||
}
|
||||
} else if (strcmp(msg_type, "COUNT") == 0) {
|
||||
// Check NIP-42 authentication for COUNT requests if required
|
||||
@@ -776,7 +786,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
send_notice_message(wsi, "NIP-42 authentication required for count requests");
|
||||
log_warning("COUNT rejected: NIP-42 authentication required");
|
||||
DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
@@ -803,7 +813,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
char filter_error[512] = {0};
|
||||
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
|
||||
send_notice_message(wsi, filter_error);
|
||||
log_warning("COUNT rejected: invalid filters");
|
||||
DEBUG_WARN("COUNT rejected: invalid filters");
|
||||
record_malformed_request(pss);
|
||||
cJSON_Delete(filters);
|
||||
cJSON_Delete(json);
|
||||
@@ -825,7 +835,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Validate subscription ID before processing
|
||||
if (!subscription_id) {
|
||||
send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
|
||||
log_warning("CLOSE rejected: NULL subscription ID");
|
||||
DEBUG_WARN("CLOSE rejected: NULL subscription ID");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
@@ -835,7 +845,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
size_t id_len = strlen(subscription_id);
|
||||
if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) {
|
||||
send_notice_message(wsi, "error: subscription ID too long or empty in CLOSE");
|
||||
log_warning("CLOSE rejected: invalid subscription ID length");
|
||||
DEBUG_WARN("CLOSE rejected: invalid subscription ID length");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
@@ -854,7 +864,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
|
||||
if (!valid_id) {
|
||||
send_notice_message(wsi, "error: invalid characters in subscription ID for CLOSE");
|
||||
log_warning("CLOSE rejected: invalid characters in subscription ID");
|
||||
DEBUG_WARN("CLOSE rejected: invalid characters in subscription ID");
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
@@ -884,7 +894,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Subscription closed
|
||||
} else {
|
||||
send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE");
|
||||
log_warning("CLOSE rejected: missing or invalid subscription ID");
|
||||
DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
|
||||
}
|
||||
} else if (strcmp(msg_type, "AUTH") == 0) {
|
||||
// Handle NIP-42 AUTH message
|
||||
@@ -899,17 +909,17 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
handle_nip42_auth_signed_event(wsi, pss, auth_payload);
|
||||
} else {
|
||||
send_notice_message(wsi, "Invalid AUTH message format");
|
||||
log_warning("Received AUTH message with invalid payload type");
|
||||
DEBUG_WARN("Received AUTH message with invalid payload type");
|
||||
}
|
||||
} else {
|
||||
send_notice_message(wsi, "AUTH message requires payload");
|
||||
log_warning("Received AUTH message without payload");
|
||||
DEBUG_WARN("Received AUTH message without payload");
|
||||
}
|
||||
} else {
|
||||
// Unknown message type
|
||||
char unknown_msg[128];
|
||||
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
|
||||
log_warning(unknown_msg);
|
||||
DEBUG_WARN(unknown_msg);
|
||||
send_notice_message(wsi, "Unknown message type");
|
||||
}
|
||||
}
|
||||
@@ -922,24 +932,27 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
|
||||
DEBUG_TRACE("WebSocket connection closed");
|
||||
DEBUG_LOG("WebSocket connection closed from %s", pss ? pss->client_ip : "unknown");
|
||||
|
||||
// Clean up session subscriptions
|
||||
if (pss) {
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
|
||||
|
||||
struct subscription* sub = pss->subscriptions;
|
||||
while (sub) {
|
||||
struct subscription* next = sub->session_next;
|
||||
remove_subscription_from_manager(sub->id, wsi);
|
||||
sub = next;
|
||||
}
|
||||
|
||||
|
||||
pss->subscriptions = NULL;
|
||||
pss->subscription_count = 0;
|
||||
|
||||
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
pthread_mutex_destroy(&pss->session_lock);
|
||||
}
|
||||
DEBUG_TRACE("WebSocket connection cleanup complete");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1041,13 +1054,13 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: port %d is not available", actual_port);
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
return -1;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
char retry_msg[256];
|
||||
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
|
||||
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
|
||||
log_warning(retry_msg);
|
||||
DEBUG_WARN(retry_msg);
|
||||
actual_port++;
|
||||
continue;
|
||||
} else {
|
||||
@@ -1055,7 +1068,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Failed to find available port after %d attempts (tried ports %d-%d)",
|
||||
max_port_attempts, configured_port, actual_port);
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -1077,14 +1090,14 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
char lws_error_msg[256];
|
||||
snprintf(lws_error_msg, sizeof(lws_error_msg),
|
||||
"libwebsockets failed to bind to port %d (errno: %d)", actual_port, errno_saved);
|
||||
log_warning(lws_error_msg);
|
||||
DEBUG_WARN(lws_error_msg);
|
||||
|
||||
port_attempts++;
|
||||
if (strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: failed to bind to port %d", actual_port);
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
break;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
actual_port++;
|
||||
@@ -1100,7 +1113,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Failed to create libwebsockets context after %d attempts. Last attempted port: %d",
|
||||
port_attempts, actual_port);
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
perror("libwebsockets creation error");
|
||||
return -1;
|
||||
}
|
||||
@@ -1110,7 +1123,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
snprintf(startup_msg, sizeof(startup_msg),
|
||||
"WebSocket relay started on ws://127.0.0.1:%d (configured port %d was unavailable)",
|
||||
actual_port, configured_port);
|
||||
log_warning(startup_msg);
|
||||
DEBUG_WARN(startup_msg);
|
||||
} else {
|
||||
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port);
|
||||
}
|
||||
@@ -1120,7 +1133,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
int result = lws_service(ws_context, 1000);
|
||||
|
||||
if (result < 0) {
|
||||
log_error("libwebsockets service error");
|
||||
DEBUG_ERROR("libwebsockets service error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1146,7 +1159,7 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey) {
|
||||
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
|
||||
return -1;
|
||||
@@ -1184,7 +1197,7 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
||||
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
|
||||
// Check if sender is admin
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!admin_pubkey || strlen(admin_pubkey) == 0 ||
|
||||
strcmp(sender_pubkey, admin_pubkey) != 0) {
|
||||
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
|
||||
@@ -1304,7 +1317,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
(void)pss; // Suppress unused parameter warning
|
||||
|
||||
if (!cJSON_IsArray(filters)) {
|
||||
log_error("COUNT filters is not an array");
|
||||
DEBUG_ERROR("COUNT filters is not an array");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1319,7 +1332,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
||||
cJSON* filter = cJSON_GetArrayItem(filters, i);
|
||||
if (!filter || !cJSON_IsObject(filter)) {
|
||||
log_warning("Invalid filter object in COUNT");
|
||||
DEBUG_WARN("Invalid filter object in COUNT");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1566,7 +1579,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
if (rc != SQLITE_OK) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to prepare COUNT query: %s", sqlite3_errmsg(g_db));
|
||||
log_error(error_msg);
|
||||
DEBUG_ERROR(error_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1661,7 +1674,7 @@ int is_client_rate_limited_for_malformed_requests(struct per_session_data *pss)
|
||||
if (pss->malformed_request_count >= MAX_MALFORMED_REQUESTS_PER_HOUR) {
|
||||
// Block for the specified duration
|
||||
pss->malformed_request_blocked_until = now + MALFORMED_REQUEST_BLOCK_DURATION;
|
||||
log_warning("Client rate limited for malformed requests");
|
||||
DEBUG_WARN("Client rate limited for malformed requests");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ Type=simple
|
||||
User=c-relay
|
||||
Group=c-relay
|
||||
WorkingDirectory=/opt/c-relay
|
||||
ExecStart=/opt/c-relay/c_relay_x86
|
||||
Environment=DEBUG_LEVEL=0
|
||||
ExecStart=/opt/c-relay/c_relay_x86 --debug-level=$DEBUG_LEVEL
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
|
||||
Reference in New Issue
Block a user