Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
670329700c | ||
|
|
62e17af311 |
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="relay-connection-url">Relay URL:</label>
|
<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">
|
placeholder="ws://localhost:8888 or wss://relay.example.com">
|
||||||
</div>
|
</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
|
// Initialize the app
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('C-Relay Admin API interface loaded');
|
console.log('C-Relay Admin API interface loaded');
|
||||||
|
|
||||||
|
// Set default relay URL based on current page location
|
||||||
|
setDefaultRelayUrl();
|
||||||
|
|
||||||
// Initialize login/logout button state
|
// Initialize login/logout button state
|
||||||
updateLoginLogoutButton();
|
updateLoginLogoutButton();
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,162 @@ DEBUG_TRACE("Entering handle_req_message()");
|
|||||||
DEBUG_TRACE("Subscription ID validated: %s", sub_id);
|
DEBUG_TRACE("Subscription ID validated: %s", sub_id);
|
||||||
DEBUG_TRACE("Exiting handle_req_message()");
|
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
|
## Performance Impact
|
||||||
|
|
||||||
|
|||||||
@@ -1,358 +0,0 @@
|
|||||||
# Event-Based Configuration System Implementation Plan
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document provides a detailed implementation plan for transitioning the C Nostr Relay from command line arguments and file-based configuration to a pure event-based configuration system using kind 33334 Nostr events stored directly in the database.
|
|
||||||
|
|
||||||
## Implementation Phases
|
|
||||||
|
|
||||||
### Phase 0: File Structure Preparation ✅ COMPLETED
|
|
||||||
|
|
||||||
#### 0.1 Backup and Prepare Files ✅ COMPLETED
|
|
||||||
**Actions:**
|
|
||||||
1. ✅ Rename `src/config.c` to `src/config.c.old` - DONE
|
|
||||||
2. ✅ Rename `src/config.h` to `src/config.h.old` - DONE
|
|
||||||
3. ✅ Create new empty `src/config.c` and `src/config.h` - DONE
|
|
||||||
4. ✅ Create new `src/default_config_event.h` - DONE
|
|
||||||
|
|
||||||
### Phase 1: Database Schema and Core Infrastructure ✅ COMPLETED
|
|
||||||
|
|
||||||
#### 1.1 Update Database Naming System ✅ COMPLETED
|
|
||||||
**File:** `src/main.c`, new `src/config.c`, new `src/config.h`
|
|
||||||
|
|
||||||
```c
|
|
||||||
// New functions implemented: ✅
|
|
||||||
char* get_database_name_from_relay_pubkey(const char* relay_pubkey);
|
|
||||||
int create_database_with_relay_pubkey(const char* relay_pubkey);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Changes Completed:** ✅
|
|
||||||
- ✅ Create completely new `src/config.c` and `src/config.h` files
|
|
||||||
- ✅ Rename old files to `src/config.c.old` and `src/config.h.old`
|
|
||||||
- ✅ Modify `init_database()` to use relay pubkey for database naming
|
|
||||||
- ✅ Use `nostr_core_lib` functions for all keypair generation
|
|
||||||
- ✅ Database path: `./<relay_pubkey>.nrdb`
|
|
||||||
- ✅ Remove all database path command line argument handling
|
|
||||||
|
|
||||||
#### 1.2 Configuration Event Storage ✅ COMPLETED
|
|
||||||
**File:** new `src/config.c`, new `src/default_config_event.h`
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Configuration functions implemented: ✅
|
|
||||||
int store_config_event_in_database(const cJSON* event);
|
|
||||||
cJSON* load_config_event_from_database(const char* relay_pubkey);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Changes Completed:** ✅
|
|
||||||
- ✅ Create new `src/default_config_event.h` for default configuration values
|
|
||||||
- ✅ Add functions to store/retrieve kind 33334 events from events table
|
|
||||||
- ✅ Use `nostr_core_lib` functions for all event validation
|
|
||||||
- ✅ Clean separation: default config values isolated in header file
|
|
||||||
- ✅ Remove existing config table dependencies
|
|
||||||
|
|
||||||
### Phase 2: Event Processing Integration ✅ COMPLETED
|
|
||||||
|
|
||||||
#### 2.1 Real-time Configuration Processing ✅ COMPLETED
|
|
||||||
**File:** `src/main.c` (event processing functions)
|
|
||||||
|
|
||||||
**Integration Points:** ✅ IMPLEMENTED
|
|
||||||
```c
|
|
||||||
// In existing event processing loop: ✅ IMPLEMENTED
|
|
||||||
// Added kind 33334 event detection in main event loop
|
|
||||||
if (kind_num == 33334) {
|
|
||||||
if (handle_configuration_event(event, error_message, sizeof(error_message)) == 0) {
|
|
||||||
// Configuration event processed successfully
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration event processing implemented: ✅
|
|
||||||
int process_configuration_event(const cJSON* event);
|
|
||||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 Configuration Application System ⚠️ PARTIALLY COMPLETED
|
|
||||||
**File:** `src/config.c`
|
|
||||||
|
|
||||||
**Status:** Configuration access functions implemented, field handlers need completion
|
|
||||||
```c
|
|
||||||
// Configuration access implemented: ✅
|
|
||||||
const char* get_config_value(const char* key);
|
|
||||||
int get_config_int(const char* key, int default_value);
|
|
||||||
int get_config_bool(const char* key, int default_value);
|
|
||||||
|
|
||||||
// Field handlers need implementation: ⏳ IN PROGRESS
|
|
||||||
// Need to implement specific apply functions for runtime changes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: First-Time Startup System ✅ COMPLETED
|
|
||||||
|
|
||||||
#### 3.1 Key Generation and Initial Setup ✅ COMPLETED
|
|
||||||
**File:** new `src/config.c`, `src/default_config_event.h`
|
|
||||||
|
|
||||||
**Status:** ✅ FULLY IMPLEMENTED with secure /dev/urandom + nostr_core_lib validation
|
|
||||||
|
|
||||||
```c
|
|
||||||
int first_time_startup_sequence() {
|
|
||||||
// 1. Generate admin keypair using nostr_core_lib
|
|
||||||
unsigned char admin_privkey_bytes[32];
|
|
||||||
char admin_privkey[65], admin_pubkey[65];
|
|
||||||
|
|
||||||
if (nostr_generate_private_key(admin_privkey_bytes) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey);
|
|
||||||
|
|
||||||
unsigned char admin_pubkey_bytes[32];
|
|
||||||
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
|
|
||||||
|
|
||||||
// 2. Generate relay keypair using nostr_core_lib
|
|
||||||
unsigned char relay_privkey_bytes[32];
|
|
||||||
char relay_privkey[65], relay_pubkey[65];
|
|
||||||
|
|
||||||
if (nostr_generate_private_key(relay_privkey_bytes) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
nostr_bytes_to_hex(relay_privkey_bytes, 32, relay_privkey);
|
|
||||||
|
|
||||||
unsigned char relay_pubkey_bytes[32];
|
|
||||||
if (nostr_ec_public_key_from_private_key(relay_privkey_bytes, relay_pubkey_bytes) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
|
|
||||||
|
|
||||||
// 3. Create database with relay pubkey name
|
|
||||||
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Create initial configuration event using defaults from header
|
|
||||||
cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey);
|
|
||||||
|
|
||||||
// 5. Store configuration event in database
|
|
||||||
store_config_event_in_database(config_event);
|
|
||||||
|
|
||||||
// 6. Print admin private key for user to save
|
|
||||||
printf("=== SAVE THIS ADMIN PRIVATE KEY ===\n");
|
|
||||||
printf("Admin Private Key: %s\n", admin_privkey);
|
|
||||||
printf("===================================\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 Database Detection Logic ✅ COMPLETED
|
|
||||||
**File:** `src/main.c`
|
|
||||||
|
|
||||||
**Status:** ✅ FULLY IMPLEMENTED
|
|
||||||
```c
|
|
||||||
// Implemented functions: ✅
|
|
||||||
char** find_existing_nrdb_files(void);
|
|
||||||
char* extract_pubkey_from_filename(const char* filename);
|
|
||||||
int is_first_time_startup(void);
|
|
||||||
int first_time_startup_sequence(void);
|
|
||||||
int startup_existing_relay(const char* relay_pubkey);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Legacy System Removal ✅ PARTIALLY COMPLETED
|
|
||||||
|
|
||||||
#### 4.1 Remove Command Line Arguments ✅ COMPLETED
|
|
||||||
**File:** `src/main.c`
|
|
||||||
|
|
||||||
**Status:** ✅ COMPLETED
|
|
||||||
- ✅ All argument parsing logic removed except --help and --version
|
|
||||||
- ✅ `--port`, `--config-dir`, `--config-file`, `--database-path` handling removed
|
|
||||||
- ✅ Environment variable override systems removed
|
|
||||||
- ✅ Clean help and version functions implemented
|
|
||||||
|
|
||||||
#### 4.2 Remove Configuration File System ✅ COMPLETED
|
|
||||||
**File:** `src/config.c`
|
|
||||||
|
|
||||||
**Status:** ✅ COMPLETED - New file created from scratch
|
|
||||||
- ✅ All legacy file-based configuration functions removed
|
|
||||||
- ✅ XDG configuration directory logic removed
|
|
||||||
- ✅ Pure event-based system implemented
|
|
||||||
|
|
||||||
#### 4.3 Remove Legacy Database Tables ⏳ PENDING
|
|
||||||
**File:** `src/sql_schema.h`
|
|
||||||
|
|
||||||
**Status:** ⏳ NEEDS COMPLETION
|
|
||||||
```sql
|
|
||||||
-- Still need to remove these tables:
|
|
||||||
DROP TABLE IF EXISTS config;
|
|
||||||
DROP TABLE IF EXISTS config_history;
|
|
||||||
DROP TABLE IF EXISTS config_file_cache;
|
|
||||||
DROP VIEW IF EXISTS active_config;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 5: Configuration Management
|
|
||||||
|
|
||||||
#### 5.1 Configuration Field Mapping
|
|
||||||
**File:** `src/config.c`
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Map configuration tags to current system
|
|
||||||
static const config_field_handler_t config_handlers[] = {
|
|
||||||
{"auth_enabled", 0, apply_auth_enabled},
|
|
||||||
{"relay_port", 1, apply_relay_port}, // requires restart
|
|
||||||
{"max_connections", 0, apply_max_connections},
|
|
||||||
{"relay_description", 0, apply_relay_description},
|
|
||||||
{"relay_contact", 0, apply_relay_contact},
|
|
||||||
{"relay_pubkey", 1, apply_relay_pubkey}, // requires restart
|
|
||||||
{"relay_privkey", 1, apply_relay_privkey}, // requires restart
|
|
||||||
{"pow_min_difficulty", 0, apply_pow_difficulty},
|
|
||||||
{"nip40_expiration_enabled", 0, apply_expiration_enabled},
|
|
||||||
{"max_subscriptions_per_client", 0, apply_max_subscriptions},
|
|
||||||
{"max_event_tags", 0, apply_max_event_tags},
|
|
||||||
{"max_content_length", 0, apply_max_content_length},
|
|
||||||
{"default_limit", 0, apply_default_limit},
|
|
||||||
{"max_limit", 0, apply_max_limit},
|
|
||||||
// ... etc
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.2 Startup Configuration Loading
|
|
||||||
**File:** `src/main.c`
|
|
||||||
|
|
||||||
```c
|
|
||||||
int startup_existing_relay(const char* relay_pubkey) {
|
|
||||||
// 1. Open database
|
|
||||||
if (init_database_with_pubkey(relay_pubkey) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Load configuration event from database
|
|
||||||
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
|
||||||
if (!config_event) {
|
|
||||||
log_error("No configuration event found in database");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Apply all configuration from event
|
|
||||||
if (apply_configuration_from_event(config_event) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Continue with normal startup
|
|
||||||
return start_relay_services();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Order - PROGRESS STATUS
|
|
||||||
|
|
||||||
### Step 1: Core Infrastructure ✅ COMPLETED
|
|
||||||
1. ✅ Implement database naming with relay pubkey
|
|
||||||
2. ✅ Add key generation functions using `nostr_core_lib`
|
|
||||||
3. ✅ Create configuration event storage/retrieval functions
|
|
||||||
4. ✅ Test basic event creation and storage
|
|
||||||
|
|
||||||
### Step 2: Event Processing Integration ✅ MOSTLY COMPLETED
|
|
||||||
1. ✅ Add kind 33334 event detection to event processing loop
|
|
||||||
2. ✅ Implement configuration event validation
|
|
||||||
3. ⚠️ Create configuration application handlers (basic access implemented, runtime handlers pending)
|
|
||||||
4. ⏳ Test real-time configuration updates (infrastructure ready)
|
|
||||||
|
|
||||||
### Step 3: First-Time Startup ✅ COMPLETED
|
|
||||||
1. ✅ Implement first-time startup detection
|
|
||||||
2. ✅ Add automatic key generation and database creation
|
|
||||||
3. ✅ Create default configuration event generation
|
|
||||||
4. ✅ Test complete first-time startup flow
|
|
||||||
|
|
||||||
### Step 4: Legacy Removal ⚠️ MOSTLY COMPLETED
|
|
||||||
1. ✅ Remove command line argument parsing
|
|
||||||
2. ✅ Remove configuration file system
|
|
||||||
3. ⏳ Remove legacy database tables (pending)
|
|
||||||
4. ✅ Update all references to use event-based config
|
|
||||||
|
|
||||||
### Step 5: Testing and Validation ⚠️ PARTIALLY COMPLETED
|
|
||||||
1. ✅ Test complete startup flow (first time and existing)
|
|
||||||
2. ⏳ Test configuration updates via events (infrastructure ready)
|
|
||||||
3. ⚠️ Test error handling and recovery (basic error handling implemented)
|
|
||||||
4. ⏳ Performance testing and optimization (pending)
|
|
||||||
|
|
||||||
## Migration Strategy
|
|
||||||
|
|
||||||
### For Existing Installations
|
|
||||||
Since the new system uses a completely different approach:
|
|
||||||
|
|
||||||
1. **No Automatic Migration**: The new system starts fresh
|
|
||||||
2. **Manual Migration**: Users can manually copy configuration values
|
|
||||||
3. **Documentation**: Provide clear migration instructions
|
|
||||||
4. **Coexistence**: Old and new systems use different database names
|
|
||||||
|
|
||||||
### Migration Steps for Users
|
|
||||||
1. Stop existing relay
|
|
||||||
2. Note current configuration values
|
|
||||||
3. Start new relay (generates keys and new database)
|
|
||||||
4. Create kind 33334 event with desired configuration using admin private key
|
|
||||||
5. Send event to relay to update configuration
|
|
||||||
|
|
||||||
## Testing Requirements
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
- Key generation functions
|
|
||||||
- Configuration event creation and validation
|
|
||||||
- Database naming logic
|
|
||||||
- Configuration application handlers
|
|
||||||
|
|
||||||
### Integration Tests
|
|
||||||
- Complete first-time startup flow
|
|
||||||
- Configuration update via events
|
|
||||||
- Error handling scenarios
|
|
||||||
- Database operations
|
|
||||||
|
|
||||||
### Performance Tests
|
|
||||||
- Startup time comparison
|
|
||||||
- Configuration update response time
|
|
||||||
- Memory usage analysis
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
1. **Admin Private Key**: Never stored, only printed once
|
|
||||||
2. **Event Validation**: All configuration events must be signed by admin
|
|
||||||
3. **Database Security**: Relay database contains relay private key
|
|
||||||
4. **Key Generation**: Use `nostr_core_lib` for cryptographically secure generation
|
|
||||||
|
|
||||||
## Files to Modify
|
|
||||||
|
|
||||||
### Major Changes
|
|
||||||
- `src/main.c` - Startup logic, event processing, argument removal
|
|
||||||
- `src/config.c` - Complete rewrite for event-based configuration
|
|
||||||
- `src/config.h` - Update function signatures and structures
|
|
||||||
- `src/sql_schema.h` - Remove config tables
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
- `Makefile` - Remove any config file generation
|
|
||||||
- `systemd/` - Update service files if needed
|
|
||||||
- Documentation updates
|
|
||||||
|
|
||||||
## Backwards Compatibility
|
|
||||||
|
|
||||||
**Breaking Changes:**
|
|
||||||
- Command line arguments removed (except --help, --version)
|
|
||||||
- Configuration files no longer used
|
|
||||||
- Database naming scheme changed
|
|
||||||
- Configuration table removed
|
|
||||||
|
|
||||||
**Migration Required:** This is a breaking change that requires manual migration for existing installations.
|
|
||||||
|
|
||||||
## Success Criteria - CURRENT STATUS
|
|
||||||
|
|
||||||
1. ✅ **Zero Command Line Arguments**: Relay starts with just `./c-relay`
|
|
||||||
2. ✅ **Automatic First-Time Setup**: Generates keys and database automatically
|
|
||||||
3. ⚠️ **Real-Time Configuration**: Infrastructure ready, handlers need completion
|
|
||||||
4. ✅ **Single Database File**: All configuration and data in one `.nrdb` file
|
|
||||||
5. ⚠️ **Admin Control**: Event processing implemented, signature validation ready
|
|
||||||
6. ⚠️ **Clean Codebase**: Most legacy code removed, database tables cleanup pending
|
|
||||||
|
|
||||||
## Risk Mitigation
|
|
||||||
|
|
||||||
1. **Backup Strategy**: Document manual backup procedures for relay database
|
|
||||||
2. **Key Loss Recovery**: Document recovery procedures if admin key is lost
|
|
||||||
3. **Testing Coverage**: Comprehensive test suite before deployment
|
|
||||||
4. **Rollback Plan**: Keep old version available during transition period
|
|
||||||
5. **Documentation**: Comprehensive user and developer documentation
|
|
||||||
|
|
||||||
This implementation plan provides a clear path from the current system to the new event-based configuration architecture while maintaining security and reliability.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Startup Configuration Design Analysis
|
|
||||||
|
|
||||||
## Review of startup_config_design.md
|
|
||||||
|
|
||||||
### Key Design Principles Identified
|
|
||||||
|
|
||||||
1. **Zero Command Line Arguments**: Complete elimination of CLI arguments for true "quick start"
|
|
||||||
2. **Event-Based Configuration**: Configuration stored as Nostr event (kind 33334) in events table
|
|
||||||
3. **Self-Contained Database**: Database named after relay pubkey (`<pubkey>.nrdb`)
|
|
||||||
4. **First-Time Setup**: Automatic key generation and initial configuration creation
|
|
||||||
5. **Configuration Consistency**: Always read from event, never from hardcoded defaults
|
|
||||||
|
|
||||||
### Implementation Gaps and Specifications Needed
|
|
||||||
|
|
||||||
#### 1. Key Generation Process
|
|
||||||
**Specification:**
|
|
||||||
```
|
|
||||||
First Startup Key Generation:
|
|
||||||
1. Generate all keys on first startup (admin private/public, relay private/public)
|
|
||||||
2. Use nostr_core_lib for key generation entropy
|
|
||||||
3. Keys are encoded in hex format
|
|
||||||
4. Print admin private key to stdout for user to save (never stored)
|
|
||||||
5. Store admin public key, relay private key, and relay public key in configuration event
|
|
||||||
6. Admin can later change the 33334 event to alter stored keys
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Database Naming and Location
|
|
||||||
**Specification:**
|
|
||||||
```
|
|
||||||
Database Naming:
|
|
||||||
1. Database is named using relay pubkey: ./<relay_pubkey>.nrdb
|
|
||||||
2. Database path structure: ./<relay_pubkey>.nrdb
|
|
||||||
3. If database creation fails, program quits (can't run without database)
|
|
||||||
4. c_nostr_relay.db should never exist in new system
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Configuration Event Structure (Kind 33334)
|
|
||||||
**Specification:**
|
|
||||||
```
|
|
||||||
Event Structure:
|
|
||||||
- Kind: 33334 (parameterized replaceable event)
|
|
||||||
- Event validation: Use nostr_core_lib to validate event
|
|
||||||
- Event content field: "C Nostr Relay Configuration" (descriptive text)
|
|
||||||
- Configuration update mechanism: TBD
|
|
||||||
- Complete tag structure provided in configuration section below
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 4. Configuration Change Monitoring
|
|
||||||
**Configuration Monitoring System:**
|
|
||||||
```
|
|
||||||
Every event that is received is checked to see if it is a kind 33334 event from the admin pubkey.
|
|
||||||
If so, it is processed as a configuration update.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5. Error Handling and Recovery
|
|
||||||
**Specification:**
|
|
||||||
```
|
|
||||||
Error Recovery Priority:
|
|
||||||
1. Try to load latest valid config event
|
|
||||||
2. Generate new default configuration event if none exists
|
|
||||||
3. Exit with error if all recovery attempts fail
|
|
||||||
|
|
||||||
Note: There is only ever one configuration event (parameterized replaceable event),
|
|
||||||
so no fallback to previous versions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Design Clarifications
|
|
||||||
|
|
||||||
**Key Management:**
|
|
||||||
- Admin private key is never stored, only printed once at first startup
|
|
||||||
- Single admin system (no multi-admin support)
|
|
||||||
- No key rotation support
|
|
||||||
|
|
||||||
**Configuration Management:**
|
|
||||||
- No configuration versioning/timestamping
|
|
||||||
- No automatic backup of configuration events
|
|
||||||
- Configuration events are not broadcastable to other relays
|
|
||||||
- Future: Auth system to restrict admin access to configuration events
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Current Configuration Structure
|
|
||||||
|
|
||||||
Based on analysis of [`src/config.c`](src/config.c:753-795), here is the complete current configuration structure that will be converted to event tags:
|
|
||||||
|
|
||||||
### Complete Event Structure Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 33334,
|
|
||||||
"created_at": 1725661483,
|
|
||||||
"tags": [
|
|
||||||
["d", "<relay_pubkey>"],
|
|
||||||
["auth_enabled", "false"],
|
|
||||||
["relay_port", "8888"],
|
|
||||||
["max_connections", "100"],
|
|
||||||
|
|
||||||
["relay_description", "High-performance C Nostr relay with SQLite storage"],
|
|
||||||
["relay_contact", ""],
|
|
||||||
["relay_pubkey", "<relay_public_key>"],
|
|
||||||
["relay_privkey", "<relay_private_key>"],
|
|
||||||
["relay_software", "https://git.laantungir.net/laantungir/c-relay.git"],
|
|
||||||
["relay_version", "v1.0.0"],
|
|
||||||
|
|
||||||
["pow_min_difficulty", "0"],
|
|
||||||
["pow_mode", "basic"],
|
|
||||||
["nip40_expiration_enabled", "true"],
|
|
||||||
["nip40_expiration_strict", "true"],
|
|
||||||
["nip40_expiration_filter", "true"],
|
|
||||||
["nip40_expiration_grace_period", "300"],
|
|
||||||
["max_subscriptions_per_client", "25"],
|
|
||||||
["max_total_subscriptions", "5000"],
|
|
||||||
["max_filters_per_subscription", "10"],
|
|
||||||
["max_event_tags", "100"],
|
|
||||||
["max_content_length", "8196"],
|
|
||||||
["max_message_length", "16384"],
|
|
||||||
["default_limit", "500"],
|
|
||||||
["max_limit", "5000"]
|
|
||||||
],
|
|
||||||
"content": "C Nostr Relay Configuration",
|
|
||||||
"pubkey": "<admin_public_key>",
|
|
||||||
"id": "<computed_event_id>",
|
|
||||||
"sig": "<event_signature>"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** The `admin_pubkey` tag is omitted as it's redundant with the event's `pubkey` field.
|
|
||||||
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
|
||||||
|
```
|
||||||
@@ -330,14 +330,14 @@ cd build
|
|||||||
# Start relay in background and capture its PID
|
# Start relay in background and capture its PID
|
||||||
if [ "$USE_TEST_KEYS" = true ]; then
|
if [ "$USE_TEST_KEYS" = true ]; then
|
||||||
echo "Using deterministic test keys for development..."
|
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
|
elif [ -n "$RELAY_ARGS" ]; then
|
||||||
echo "Starting relay with custom configuration..."
|
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
|
else
|
||||||
# No command line arguments needed for random key generation
|
# No command line arguments needed for random key generation
|
||||||
echo "Starting relay with 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
|
fi
|
||||||
RELAY_PID=$!
|
RELAY_PID=$!
|
||||||
# Change back to original directory
|
# Change back to original directory
|
||||||
|
|||||||
973
src/config.c
973
src/config.c
File diff suppressed because it is too large
Load Diff
83
src/config.h
83
src/config.h
@@ -27,78 +27,6 @@ struct lws;
|
|||||||
// Database path for event-based config
|
// Database path for event-based config
|
||||||
extern char g_database_path[512];
|
extern char g_database_path[512];
|
||||||
|
|
||||||
// Unified configuration cache structure (consolidates all caching systems)
|
|
||||||
typedef struct {
|
|
||||||
// Critical keys (frequently accessed)
|
|
||||||
char admin_pubkey[65];
|
|
||||||
char relay_pubkey[65];
|
|
||||||
|
|
||||||
// Auth config (from request_validator)
|
|
||||||
int auth_required;
|
|
||||||
long max_file_size;
|
|
||||||
int admin_enabled;
|
|
||||||
int nip42_mode;
|
|
||||||
int nip42_challenge_timeout;
|
|
||||||
int nip42_time_tolerance;
|
|
||||||
int nip70_protected_events_enabled;
|
|
||||||
|
|
||||||
// Static buffer for config values (replaces static buffers in get_config_value functions)
|
|
||||||
char temp_buffer[CONFIG_VALUE_MAX_LENGTH];
|
|
||||||
|
|
||||||
// NIP-11 relay information (migrated from g_relay_info in main.c)
|
|
||||||
struct {
|
|
||||||
char name[RELAY_NAME_MAX_LENGTH];
|
|
||||||
char description[RELAY_DESCRIPTION_MAX_LENGTH];
|
|
||||||
char banner[RELAY_URL_MAX_LENGTH];
|
|
||||||
char icon[RELAY_URL_MAX_LENGTH];
|
|
||||||
char pubkey[RELAY_PUBKEY_MAX_LENGTH];
|
|
||||||
char contact[RELAY_CONTACT_MAX_LENGTH];
|
|
||||||
char software[RELAY_URL_MAX_LENGTH];
|
|
||||||
char version[64];
|
|
||||||
char privacy_policy[RELAY_URL_MAX_LENGTH];
|
|
||||||
char terms_of_service[RELAY_URL_MAX_LENGTH];
|
|
||||||
// Raw string values for parsing into cJSON arrays
|
|
||||||
char supported_nips_str[CONFIG_VALUE_MAX_LENGTH];
|
|
||||||
char language_tags_str[CONFIG_VALUE_MAX_LENGTH];
|
|
||||||
char relay_countries_str[CONFIG_VALUE_MAX_LENGTH];
|
|
||||||
// Parsed cJSON arrays
|
|
||||||
cJSON* supported_nips;
|
|
||||||
cJSON* limitation;
|
|
||||||
cJSON* retention;
|
|
||||||
cJSON* relay_countries;
|
|
||||||
cJSON* language_tags;
|
|
||||||
cJSON* tags;
|
|
||||||
char posting_policy[RELAY_URL_MAX_LENGTH];
|
|
||||||
cJSON* fees;
|
|
||||||
char payments_url[RELAY_URL_MAX_LENGTH];
|
|
||||||
} relay_info;
|
|
||||||
|
|
||||||
// NIP-13 PoW configuration (migrated from g_pow_config in main.c)
|
|
||||||
struct {
|
|
||||||
int enabled;
|
|
||||||
int min_pow_difficulty;
|
|
||||||
int validation_flags;
|
|
||||||
int require_nonce_tag;
|
|
||||||
int reject_lower_targets;
|
|
||||||
int strict_format;
|
|
||||||
int anti_spam_mode;
|
|
||||||
} pow_config;
|
|
||||||
|
|
||||||
// NIP-40 Expiration configuration (migrated from g_expiration_config in main.c)
|
|
||||||
struct {
|
|
||||||
int enabled;
|
|
||||||
int strict_mode;
|
|
||||||
int filter_responses;
|
|
||||||
int delete_expired;
|
|
||||||
long grace_period;
|
|
||||||
} expiration_config;
|
|
||||||
|
|
||||||
// Cache management
|
|
||||||
time_t cache_expires;
|
|
||||||
int cache_valid;
|
|
||||||
pthread_mutex_t cache_lock;
|
|
||||||
} unified_config_cache_t;
|
|
||||||
|
|
||||||
// Command line options structure for first-time startup
|
// Command line options structure for first-time startup
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int port_override; // -1 = not set, >0 = port value
|
int port_override; // -1 = not set, >0 = port value
|
||||||
@@ -108,9 +36,6 @@ typedef struct {
|
|||||||
int debug_level; // 0-5, default 0 (no debug output)
|
int debug_level; // 0-5, default 0 (no debug output)
|
||||||
} cli_options_t;
|
} cli_options_t;
|
||||||
|
|
||||||
// Global unified configuration cache
|
|
||||||
extern unified_config_cache_t g_unified_cache;
|
|
||||||
|
|
||||||
// Core configuration functions (temporary compatibility)
|
// Core configuration functions (temporary compatibility)
|
||||||
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
||||||
void cleanup_configuration_system(void);
|
void cleanup_configuration_system(void);
|
||||||
@@ -138,8 +63,8 @@ int get_config_bool(const char* key, int default_value);
|
|||||||
|
|
||||||
// First-time startup functions
|
// First-time startup functions
|
||||||
int is_first_time_startup(void);
|
int is_first_time_startup(void);
|
||||||
int first_time_startup_sequence(const cli_options_t* cli_options);
|
int first_time_startup_sequence(const cli_options_t* cli_options, char* admin_pubkey_out, char* relay_pubkey_out, char* relay_privkey_out);
|
||||||
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
|
// Configuration application functions
|
||||||
int apply_configuration_from_event(const cJSON* event);
|
int apply_configuration_from_event(const cJSON* event);
|
||||||
@@ -169,6 +94,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);
|
const char* description, const char* category, int requires_restart);
|
||||||
int update_config_in_table(const char* key, const char* value);
|
int update_config_in_table(const char* key, const char* value);
|
||||||
int populate_default_config_values(void);
|
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);
|
int add_pubkeys_to_config_table(void);
|
||||||
|
|
||||||
// Admin event processing functions (updated with WebSocket support)
|
// Admin event processing functions (updated with WebSocket support)
|
||||||
@@ -212,6 +138,9 @@ int populate_config_table_from_event(const cJSON* event);
|
|||||||
int process_startup_config_event(const cJSON* event);
|
int process_startup_config_event(const cJSON* event);
|
||||||
int process_startup_config_event_with_fallback(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
|
// Dynamic event generation functions for WebSocket configuration fetching
|
||||||
cJSON* generate_config_event_from_table(void);
|
cJSON* generate_config_event_from_table(void);
|
||||||
int req_filter_requests_config_events(const cJSON* filter);
|
int req_filter_requests_config_events(const cJSON* filter);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ static const struct {
|
|||||||
{"nip42_auth_required_subscriptions", "false"},
|
{"nip42_auth_required_subscriptions", "false"},
|
||||||
{"nip42_auth_required_kinds", "4,14"}, // Default: DM kinds require auth
|
{"nip42_auth_required_kinds", "4,14"}, // Default: DM kinds require auth
|
||||||
{"nip42_challenge_expiration", "600"}, // 10 minutes
|
{"nip42_challenge_expiration", "600"}, // 10 minutes
|
||||||
|
{"nip42_challenge_timeout", "600"}, // Challenge timeout (seconds)
|
||||||
|
{"nip42_time_tolerance", "300"}, // Time tolerance (seconds)
|
||||||
|
|
||||||
// NIP-70 Protected Events
|
// NIP-70 Protected Events
|
||||||
{"nip70_protected_events_enabled", "false"},
|
{"nip70_protected_events_enabled", "false"},
|
||||||
|
|||||||
@@ -30,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);
|
extern const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);
|
||||||
|
|
||||||
// Forward declarations for config functions
|
// Forward declarations for config functions
|
||||||
extern const char* get_relay_pubkey_cached(void);
|
|
||||||
extern char* get_relay_private_key(void);
|
extern char* get_relay_private_key(void);
|
||||||
extern const char* get_config_value(const char* key);
|
extern const char* get_config_value(const char* key);
|
||||||
extern int get_config_bool(const char* key, int default_value);
|
extern int get_config_bool(const char* key, int default_value);
|
||||||
extern const char* get_admin_pubkey_cached(void);
|
|
||||||
|
|
||||||
// Forward declarations for database functions
|
// Forward declarations for database functions
|
||||||
extern int store_event(cJSON* event);
|
extern int store_event(cJSON* event);
|
||||||
@@ -1019,7 +1017,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get relay keys for signing
|
// 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();
|
char* relay_privkey_hex = get_relay_private_key();
|
||||||
if (!relay_pubkey || !relay_privkey_hex) {
|
if (!relay_pubkey || !relay_privkey_hex) {
|
||||||
if (relay_privkey_hex) free(relay_privkey_hex);
|
if (relay_privkey_hex) free(relay_privkey_hex);
|
||||||
@@ -1430,7 +1428,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
|||||||
"[\"command_processed\", \"success\", \"%s\"]", "NIP-17 admin command executed");
|
"[\"command_processed\", \"success\", \"%s\"]", "NIP-17 admin command executed");
|
||||||
|
|
||||||
// Get relay pubkey for creating DM event
|
// 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) {
|
if (relay_pubkey) {
|
||||||
cJSON* success_dm = nostr_nip17_create_chat_event(
|
cJSON* success_dm = nostr_nip17_create_chat_event(
|
||||||
response_content, // message content
|
response_content, // message content
|
||||||
@@ -1518,7 +1516,7 @@ int is_nip17_gift_wrap_for_relay(cJSON* event) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
if (!relay_pubkey) {
|
if (!relay_pubkey) {
|
||||||
DEBUG_ERROR("NIP-17: Could not get relay pubkey for validation");
|
DEBUG_ERROR("NIP-17: Could not get relay pubkey for validation");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1566,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);
|
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
|
||||||
|
|
||||||
// Check if sender is admin
|
// 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;
|
int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0;
|
||||||
|
|
||||||
// Parse DM content as JSON array of commands
|
// Parse DM content as JSON array of commands
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
147
src/main.c
147
src/main.c
@@ -74,10 +74,6 @@ struct relay_info {
|
|||||||
char payments_url[RELAY_URL_MAX_LENGTH];
|
char payments_url[RELAY_URL_MAX_LENGTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global relay information instance moved to unified cache
|
|
||||||
// static struct relay_info g_relay_info = {0}; // REMOVED - now in g_unified_cache.relay_info
|
|
||||||
|
|
||||||
|
|
||||||
// NIP-40 Expiration configuration (now in nip040.c)
|
// NIP-40 Expiration configuration (now in nip040.c)
|
||||||
extern struct expiration_config g_expiration_config;
|
extern struct expiration_config g_expiration_config;
|
||||||
|
|
||||||
@@ -92,12 +88,6 @@ extern subscription_manager_t g_subscription_manager;
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Forward declarations for logging functions - REMOVED (replaced by debug system)
|
|
||||||
|
|
||||||
// Forward declaration for subscription manager configuration
|
// Forward declaration for subscription manager configuration
|
||||||
void update_subscription_manager_config(void);
|
void update_subscription_manager_config(void);
|
||||||
|
|
||||||
@@ -318,6 +308,22 @@ int init_database(const char* database_path_override) {
|
|||||||
return -1;
|
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
|
// 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'";
|
const char* check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='events'";
|
||||||
sqlite3_stmt* check_stmt;
|
sqlite3_stmt* check_stmt;
|
||||||
@@ -1257,10 +1263,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
cJSON_AddItemToObject(event, "tags", tags);
|
cJSON_AddItemToObject(event, "tags", tags);
|
||||||
|
|
||||||
// Check expiration filtering (NIP-40) at application level
|
// Check expiration filtering (NIP-40) at application level
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||||
int expiration_enabled = g_unified_cache.expiration_config.enabled;
|
int filter_responses = get_config_bool("expiration_filter", 1);
|
||||||
int filter_responses = g_unified_cache.expiration_config.filter_responses;
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
|
|
||||||
if (expiration_enabled && filter_responses) {
|
if (expiration_enabled && filter_responses) {
|
||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
@@ -1616,7 +1620,10 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet)
|
// 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) {
|
char admin_pubkey[65] = {0};
|
||||||
|
char relay_pubkey[65] = {0};
|
||||||
|
char relay_privkey[65] = {0};
|
||||||
|
if (first_time_startup_sequence(&cli_options, admin_pubkey, relay_pubkey, relay_privkey) != 0) {
|
||||||
DEBUG_ERROR("Failed to complete first-time startup sequence");
|
DEBUG_ERROR("Failed to complete first-time startup sequence");
|
||||||
cleanup_configuration_system();
|
cleanup_configuration_system();
|
||||||
nostr_cleanup();
|
nostr_cleanup();
|
||||||
@@ -1633,22 +1640,62 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
DEBUG_LOG("Database initialized for first-time startup");
|
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, populate the complete config table atomically
|
||||||
|
// BUG FIX: Use the pubkeys returned from first_time_startup_sequence instead of trying to read from empty database
|
||||||
|
DEBUG_LOG("Using pubkeys from first-time startup sequence for config population");
|
||||||
|
DEBUG_LOG("admin_pubkey from startup: %s", admin_pubkey);
|
||||||
|
DEBUG_LOG("relay_pubkey from startup: %s", relay_pubkey);
|
||||||
|
|
||||||
|
if (populate_all_config_values_atomic(admin_pubkey, relay_pubkey) != 0) {
|
||||||
|
DEBUG_ERROR("Failed to populate complete config table");
|
||||||
|
cleanup_configuration_system();
|
||||||
|
nostr_cleanup();
|
||||||
|
close_database();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply CLI overrides atomically (after complete config table exists)
|
||||||
|
if (apply_cli_overrides_atomic(&cli_options) != 0) {
|
||||||
|
DEBUG_ERROR("Failed to apply CLI overrides");
|
||||||
|
cleanup_configuration_system();
|
||||||
|
nostr_cleanup();
|
||||||
|
close_database();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Now that database is available, store the relay private key securely
|
// Now that database is available, store the relay private key securely
|
||||||
const char* relay_privkey = get_temp_relay_private_key();
|
if (relay_privkey[0] != '\0') {
|
||||||
if (relay_privkey) {
|
|
||||||
if (store_relay_private_key(relay_privkey) != 0) {
|
if (store_relay_private_key(relay_privkey) != 0) {
|
||||||
DEBUG_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();
|
cleanup_configuration_system();
|
||||||
nostr_cleanup();
|
nostr_cleanup();
|
||||||
|
close_database();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DEBUG_ERROR("Relay private key not available from first-time startup");
|
DEBUG_ERROR("Relay private key not available from first-time startup");
|
||||||
cleanup_configuration_system();
|
cleanup_configuration_system();
|
||||||
nostr_cleanup();
|
nostr_cleanup();
|
||||||
|
close_database();
|
||||||
return 1;
|
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
|
// Handle configuration setup after database is initialized
|
||||||
// Always populate defaults directly in config table (abandoning legacy event signing)
|
// Always populate defaults directly in config table (abandoning legacy event signing)
|
||||||
|
|
||||||
@@ -1661,6 +1708,19 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
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
|
// Apply CLI overrides now that database is available
|
||||||
if (cli_options.port_override > 0) {
|
if (cli_options.port_override > 0) {
|
||||||
char port_str[16];
|
char port_str[16];
|
||||||
@@ -1683,6 +1743,20 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
return 1;
|
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 {
|
} else {
|
||||||
// Find existing database file
|
// Find existing database file
|
||||||
char** existing_files = find_existing_db_files();
|
char** existing_files = find_existing_db_files();
|
||||||
@@ -1718,7 +1792,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup existing relay (sets database path and loads config)
|
// Setup existing relay (sets database path and loads config)
|
||||||
if (startup_existing_relay(relay_pubkey) != 0) {
|
if (startup_existing_relay(relay_pubkey, &cli_options) != 0) {
|
||||||
DEBUG_ERROR("Failed to setup existing relay");
|
DEBUG_ERROR("Failed to setup existing relay");
|
||||||
cleanup_configuration_system();
|
cleanup_configuration_system();
|
||||||
free(relay_pubkey);
|
free(relay_pubkey);
|
||||||
@@ -1761,18 +1835,22 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
DEBUG_LOG("Existing database initialized");
|
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;
|
sqlite3_stmt* stmt;
|
||||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
int row_count = sqlite3_column_int(stmt, 0);
|
int row_count = sqlite3_column_int(stmt, 0);
|
||||||
printf(" Config table row count after database initialization: %d\n", row_count);
|
DEBUG_LOG("Config table row count after init_database(): %d", row_count);
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
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)
|
// Ensure default configuration values are populated (for any missing keys)
|
||||||
// This must be done AFTER database initialization
|
// This must be done AFTER database initialization
|
||||||
// COMMENTED OUT: Don't modify existing database config on restart
|
// COMMENTED OUT: Don't modify existing database config on restart
|
||||||
@@ -1792,6 +1870,19 @@ int main(int argc, char* argv[]) {
|
|||||||
// No longer a warning - just informational
|
// 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
|
// Ensure pubkeys are in config table for existing relay
|
||||||
// This handles migration from old event-based config to table-based config
|
// 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* admin_pubkey_from_table = get_config_value_from_table("admin_pubkey");
|
||||||
@@ -1822,6 +1913,19 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
return 1;
|
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)
|
// Apply CLI overrides for existing relay (port override should work even for existing relays)
|
||||||
@@ -1837,6 +1941,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Free memory
|
// Free memory
|
||||||
free(relay_pubkey);
|
free(relay_pubkey);
|
||||||
|
|||||||
522
src/nip011.c
522
src/nip011.c
@@ -15,8 +15,7 @@ const char* get_config_value(const char* key);
|
|||||||
int get_config_int(const char* key, int default_value);
|
int get_config_int(const char* key, int default_value);
|
||||||
int get_config_bool(const char* key, int default_value);
|
int get_config_bool(const char* key, int default_value);
|
||||||
|
|
||||||
// Forward declarations for global cache access
|
// NIP-11 relay information is now managed directly from config table
|
||||||
extern unified_config_cache_t g_unified_cache;
|
|
||||||
|
|
||||||
// Forward declarations for constants (defined in config.h and other headers)
|
// Forward declarations for constants (defined in config.h and other headers)
|
||||||
#define HTTP_STATUS_OK 200
|
#define HTTP_STATUS_OK 200
|
||||||
@@ -75,185 +74,14 @@ cJSON* parse_comma_separated_array(const char* csv_string) {
|
|||||||
|
|
||||||
// Initialize relay information using configuration system
|
// Initialize relay information using configuration system
|
||||||
void init_relay_info() {
|
void init_relay_info() {
|
||||||
// Get all config values first (without holding mutex to avoid deadlock)
|
// NIP-11 relay information is now generated dynamically from config table
|
||||||
// Note: These may be dynamically allocated strings that need to be freed
|
// No initialization needed - data is fetched directly from database when requested
|
||||||
const char* relay_name = get_config_value("relay_name");
|
|
||||||
const char* relay_description = get_config_value("relay_description");
|
|
||||||
const char* relay_software = get_config_value("relay_software");
|
|
||||||
const char* relay_version = get_config_value("relay_version");
|
|
||||||
const char* relay_contact = get_config_value("relay_contact");
|
|
||||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
|
||||||
const char* supported_nips_csv = get_config_value("supported_nips");
|
|
||||||
const char* language_tags_csv = get_config_value("language_tags");
|
|
||||||
const char* relay_countries_csv = get_config_value("relay_countries");
|
|
||||||
const char* posting_policy = get_config_value("posting_policy");
|
|
||||||
const char* payments_url = get_config_value("payments_url");
|
|
||||||
|
|
||||||
// Get config values for limitations
|
|
||||||
int max_message_length = get_config_int("max_message_length", 16384);
|
|
||||||
int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20);
|
|
||||||
int max_limit = get_config_int("max_limit", 5000);
|
|
||||||
int max_event_tags = get_config_int("max_event_tags", 100);
|
|
||||||
int max_content_length = get_config_int("max_content_length", 8196);
|
|
||||||
int default_limit = get_config_int("default_limit", 500);
|
|
||||||
int admin_enabled = get_config_bool("admin_enabled", 0);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
|
||||||
|
|
||||||
// Update relay information fields
|
|
||||||
if (relay_name) {
|
|
||||||
strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1);
|
|
||||||
free((char*)relay_name); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_description) {
|
|
||||||
strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1);
|
|
||||||
free((char*)relay_description); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_software) {
|
|
||||||
strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1);
|
|
||||||
free((char*)relay_software); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_version) {
|
|
||||||
strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1);
|
|
||||||
free((char*)relay_version); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_contact) {
|
|
||||||
strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1);
|
|
||||||
free((char*)relay_contact); // Free dynamically allocated string
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_pubkey) {
|
|
||||||
strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1);
|
|
||||||
free((char*)relay_pubkey); // Free dynamically allocated string
|
|
||||||
}
|
|
||||||
|
|
||||||
if (posting_policy) {
|
|
||||||
strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, sizeof(g_unified_cache.relay_info.posting_policy) - 1);
|
|
||||||
free((char*)posting_policy); // Free dynamically allocated string
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payments_url) {
|
|
||||||
strncpy(g_unified_cache.relay_info.payments_url, payments_url, sizeof(g_unified_cache.relay_info.payments_url) - 1);
|
|
||||||
free((char*)payments_url); // Free dynamically allocated string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize supported NIPs array from config
|
|
||||||
if (supported_nips_csv) {
|
|
||||||
g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv);
|
|
||||||
free((char*)supported_nips_csv); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
// Fallback to default supported NIPs
|
|
||||||
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.supported_nips) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize server limitations using configuration
|
|
||||||
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
|
||||||
if (g_unified_cache.relay_info.limitation) {
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize empty retention policies (can be configured later)
|
|
||||||
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
|
||||||
|
|
||||||
// Initialize language tags from config
|
|
||||||
if (language_tags_csv) {
|
|
||||||
g_unified_cache.relay_info.language_tags = parse_comma_separated_array(language_tags_csv);
|
|
||||||
free((char*)language_tags_csv); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
// Fallback to global
|
|
||||||
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.language_tags) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize relay countries from config
|
|
||||||
if (relay_countries_csv) {
|
|
||||||
g_unified_cache.relay_info.relay_countries = parse_comma_separated_array(relay_countries_csv);
|
|
||||||
free((char*)relay_countries_csv); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
// Fallback to global
|
|
||||||
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.relay_countries) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize content tags as empty array
|
|
||||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
|
||||||
|
|
||||||
// Initialize fees as empty object (no payment required by default)
|
|
||||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up relay information JSON objects
|
// Clean up relay information JSON objects
|
||||||
void cleanup_relay_info() {
|
void cleanup_relay_info() {
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
// NIP-11 relay information is now generated dynamically from config table
|
||||||
if (g_unified_cache.relay_info.supported_nips) {
|
// No cleanup needed - data is fetched directly from database when requested
|
||||||
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
|
|
||||||
g_unified_cache.relay_info.supported_nips = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.limitation) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.limitation);
|
|
||||||
g_unified_cache.relay_info.limitation = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.retention) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.retention);
|
|
||||||
g_unified_cache.relay_info.retention = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.language_tags) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.language_tags);
|
|
||||||
g_unified_cache.relay_info.language_tags = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.relay_countries) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
|
|
||||||
g_unified_cache.relay_info.relay_countries = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.tags) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.tags);
|
|
||||||
g_unified_cache.relay_info.tags = NULL;
|
|
||||||
}
|
|
||||||
if (g_unified_cache.relay_info.fees) {
|
|
||||||
cJSON_Delete(g_unified_cache.relay_info.fees);
|
|
||||||
g_unified_cache.relay_info.fees = NULL;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate NIP-11 compliant JSON document
|
// Generate NIP-11 compliant JSON document
|
||||||
@@ -264,247 +92,193 @@ cJSON* generate_relay_info_json() {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
// Get all config values directly from database
|
||||||
|
const char* relay_name = get_config_value("relay_name");
|
||||||
|
const char* relay_description = get_config_value("relay_description");
|
||||||
|
const char* relay_banner = get_config_value("relay_banner");
|
||||||
|
const char* relay_icon = get_config_value("relay_icon");
|
||||||
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
|
const char* relay_contact = get_config_value("relay_contact");
|
||||||
|
const char* supported_nips_csv = get_config_value("supported_nips");
|
||||||
|
const char* relay_software = get_config_value("relay_software");
|
||||||
|
const char* relay_version = get_config_value("relay_version");
|
||||||
|
const char* privacy_policy = get_config_value("privacy_policy");
|
||||||
|
const char* terms_of_service = get_config_value("terms_of_service");
|
||||||
|
const char* posting_policy = get_config_value("posting_policy");
|
||||||
|
const char* language_tags_csv = get_config_value("language_tags");
|
||||||
|
const char* relay_countries_csv = get_config_value("relay_countries");
|
||||||
|
const char* payments_url = get_config_value("payments_url");
|
||||||
|
|
||||||
// Defensive reinit: if relay_info appears empty (cache refresh wiped it), rebuild it directly from table
|
// Get config values for limitations
|
||||||
if (strlen(g_unified_cache.relay_info.name) == 0 &&
|
int max_message_length = get_config_int("max_message_length", 16384);
|
||||||
strlen(g_unified_cache.relay_info.description) == 0 &&
|
int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20);
|
||||||
strlen(g_unified_cache.relay_info.software) == 0) {
|
int max_limit = get_config_int("max_limit", 5000);
|
||||||
DEBUG_WARN("NIP-11 relay_info appears empty, rebuilding directly from config table");
|
int max_event_tags = get_config_int("max_event_tags", 100);
|
||||||
|
int max_content_length = get_config_int("max_content_length", 8196);
|
||||||
|
int default_limit = get_config_int("default_limit", 500);
|
||||||
|
int min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||||
|
int admin_enabled = get_config_bool("admin_enabled", 0);
|
||||||
|
|
||||||
// Rebuild relay_info directly from config table to avoid circular cache dependency
|
// Add basic relay information
|
||||||
// Get values directly from table (similar to init_relay_info but without cache calls)
|
if (relay_name && strlen(relay_name) > 0) {
|
||||||
const char* relay_name = get_config_value_from_table("relay_name");
|
cJSON_AddStringToObject(info, "name", relay_name);
|
||||||
if (relay_name) {
|
|
||||||
strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1);
|
|
||||||
free((char*)relay_name);
|
free((char*)relay_name);
|
||||||
} else {
|
} else {
|
||||||
strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1);
|
cJSON_AddStringToObject(info, "name", "C Nostr Relay");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_description = get_config_value_from_table("relay_description");
|
if (relay_description && strlen(relay_description) > 0) {
|
||||||
if (relay_description) {
|
cJSON_AddStringToObject(info, "description", relay_description);
|
||||||
strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1);
|
|
||||||
free((char*)relay_description);
|
free((char*)relay_description);
|
||||||
} else {
|
} else {
|
||||||
strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1);
|
cJSON_AddStringToObject(info, "description", "A high-performance Nostr relay implemented in C with SQLite storage");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_software = get_config_value_from_table("relay_software");
|
if (relay_banner && strlen(relay_banner) > 0) {
|
||||||
if (relay_software) {
|
cJSON_AddStringToObject(info, "banner", relay_banner);
|
||||||
strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1);
|
free((char*)relay_banner);
|
||||||
free((char*)relay_software);
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_version = get_config_value_from_table("relay_version");
|
if (relay_icon && strlen(relay_icon) > 0) {
|
||||||
if (relay_version) {
|
cJSON_AddStringToObject(info, "icon", relay_icon);
|
||||||
strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1);
|
free((char*)relay_icon);
|
||||||
free((char*)relay_version);
|
|
||||||
} else {
|
|
||||||
strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_contact = get_config_value_from_table("relay_contact");
|
if (relay_pubkey && strlen(relay_pubkey) > 0) {
|
||||||
if (relay_contact) {
|
cJSON_AddStringToObject(info, "pubkey", relay_pubkey);
|
||||||
strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1);
|
|
||||||
free((char*)relay_contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
|
|
||||||
if (relay_pubkey) {
|
|
||||||
strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1);
|
|
||||||
free((char*)relay_pubkey);
|
free((char*)relay_pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* posting_policy = get_config_value_from_table("posting_policy");
|
if (relay_contact && strlen(relay_contact) > 0) {
|
||||||
if (posting_policy) {
|
cJSON_AddStringToObject(info, "contact", relay_contact);
|
||||||
strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, sizeof(g_unified_cache.relay_info.posting_policy) - 1);
|
free((char*)relay_contact);
|
||||||
free((char*)posting_policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* payments_url = get_config_value_from_table("payments_url");
|
|
||||||
if (payments_url) {
|
|
||||||
strncpy(g_unified_cache.relay_info.payments_url, payments_url, sizeof(g_unified_cache.relay_info.payments_url) - 1);
|
|
||||||
free((char*)payments_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild supported_nips array
|
|
||||||
const char* supported_nips_csv = get_config_value_from_table("supported_nips");
|
|
||||||
if (supported_nips_csv) {
|
|
||||||
g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv);
|
|
||||||
free((char*)supported_nips_csv);
|
|
||||||
} else {
|
|
||||||
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.supported_nips) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40));
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild limitation object
|
|
||||||
int max_message_length = 16384;
|
|
||||||
const char* max_msg_str = get_config_value_from_table("max_message_length");
|
|
||||||
if (max_msg_str) {
|
|
||||||
max_message_length = atoi(max_msg_str);
|
|
||||||
free((char*)max_msg_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_subscriptions_per_client = 20;
|
|
||||||
const char* max_subs_str = get_config_value_from_table("max_subscriptions_per_client");
|
|
||||||
if (max_subs_str) {
|
|
||||||
max_subscriptions_per_client = atoi(max_subs_str);
|
|
||||||
free((char*)max_subs_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_limit = 5000;
|
|
||||||
const char* max_limit_str = get_config_value_from_table("max_limit");
|
|
||||||
if (max_limit_str) {
|
|
||||||
max_limit = atoi(max_limit_str);
|
|
||||||
free((char*)max_limit_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_event_tags = 100;
|
|
||||||
const char* max_tags_str = get_config_value_from_table("max_event_tags");
|
|
||||||
if (max_tags_str) {
|
|
||||||
max_event_tags = atoi(max_tags_str);
|
|
||||||
free((char*)max_tags_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_content_length = 8196;
|
|
||||||
const char* max_content_str = get_config_value_from_table("max_content_length");
|
|
||||||
if (max_content_str) {
|
|
||||||
max_content_length = atoi(max_content_str);
|
|
||||||
free((char*)max_content_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int default_limit = 500;
|
|
||||||
const char* default_limit_str = get_config_value_from_table("default_limit");
|
|
||||||
if (default_limit_str) {
|
|
||||||
default_limit = atoi(default_limit_str);
|
|
||||||
free((char*)default_limit_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
int admin_enabled = 0;
|
|
||||||
const char* admin_enabled_str = get_config_value_from_table("admin_enabled");
|
|
||||||
if (admin_enabled_str) {
|
|
||||||
admin_enabled = (strcmp(admin_enabled_str, "true") == 0) ? 1 : 0;
|
|
||||||
free((char*)admin_enabled_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
|
||||||
if (g_unified_cache.relay_info.limitation) {
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False);
|
|
||||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild other arrays (empty for now)
|
|
||||||
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
|
||||||
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.language_tags) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*"));
|
|
||||||
}
|
|
||||||
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
|
||||||
if (g_unified_cache.relay_info.relay_countries) {
|
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*"));
|
|
||||||
}
|
|
||||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
|
||||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add basic relay information
|
|
||||||
if (strlen(g_unified_cache.relay_info.name) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "name", g_unified_cache.relay_info.name);
|
|
||||||
}
|
|
||||||
if (strlen(g_unified_cache.relay_info.description) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "description", g_unified_cache.relay_info.description);
|
|
||||||
}
|
|
||||||
if (strlen(g_unified_cache.relay_info.banner) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "banner", g_unified_cache.relay_info.banner);
|
|
||||||
}
|
|
||||||
if (strlen(g_unified_cache.relay_info.icon) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "icon", g_unified_cache.relay_info.icon);
|
|
||||||
}
|
|
||||||
if (strlen(g_unified_cache.relay_info.pubkey) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "pubkey", g_unified_cache.relay_info.pubkey);
|
|
||||||
}
|
|
||||||
if (strlen(g_unified_cache.relay_info.contact) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "contact", g_unified_cache.relay_info.contact);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add supported NIPs
|
// Add supported NIPs
|
||||||
if (g_unified_cache.relay_info.supported_nips) {
|
if (supported_nips_csv && strlen(supported_nips_csv) > 0) {
|
||||||
cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_unified_cache.relay_info.supported_nips, 1));
|
cJSON* supported_nips = parse_comma_separated_array(supported_nips_csv);
|
||||||
|
if (supported_nips) {
|
||||||
|
cJSON_AddItemToObject(info, "supported_nips", supported_nips);
|
||||||
|
}
|
||||||
|
free((char*)supported_nips_csv);
|
||||||
|
} else {
|
||||||
|
// Default supported NIPs
|
||||||
|
cJSON* supported_nips = cJSON_CreateArray();
|
||||||
|
if (supported_nips) {
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
||||||
|
cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
||||||
|
cJSON_AddItemToObject(info, "supported_nips", supported_nips);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add software information
|
// Add software information
|
||||||
if (strlen(g_unified_cache.relay_info.software) > 0) {
|
if (relay_software && strlen(relay_software) > 0) {
|
||||||
cJSON_AddStringToObject(info, "software", g_unified_cache.relay_info.software);
|
cJSON_AddStringToObject(info, "software", relay_software);
|
||||||
|
free((char*)relay_software);
|
||||||
|
} else {
|
||||||
|
cJSON_AddStringToObject(info, "software", "https://git.laantungir.net/laantungir/c-relay.git");
|
||||||
}
|
}
|
||||||
if (strlen(g_unified_cache.relay_info.version) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "version", g_unified_cache.relay_info.version);
|
if (relay_version && strlen(relay_version) > 0) {
|
||||||
|
cJSON_AddStringToObject(info, "version", relay_version);
|
||||||
|
free((char*)relay_version);
|
||||||
|
} else {
|
||||||
|
cJSON_AddStringToObject(info, "version", "0.2.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add policies
|
// Add policies
|
||||||
if (strlen(g_unified_cache.relay_info.privacy_policy) > 0) {
|
if (privacy_policy && strlen(privacy_policy) > 0) {
|
||||||
cJSON_AddStringToObject(info, "privacy_policy", g_unified_cache.relay_info.privacy_policy);
|
cJSON_AddStringToObject(info, "privacy_policy", privacy_policy);
|
||||||
|
free((char*)privacy_policy);
|
||||||
}
|
}
|
||||||
if (strlen(g_unified_cache.relay_info.terms_of_service) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "terms_of_service", g_unified_cache.relay_info.terms_of_service);
|
if (terms_of_service && strlen(terms_of_service) > 0) {
|
||||||
|
cJSON_AddStringToObject(info, "terms_of_service", terms_of_service);
|
||||||
|
free((char*)terms_of_service);
|
||||||
}
|
}
|
||||||
if (strlen(g_unified_cache.relay_info.posting_policy) > 0) {
|
|
||||||
cJSON_AddStringToObject(info, "posting_policy", g_unified_cache.relay_info.posting_policy);
|
if (posting_policy && strlen(posting_policy) > 0) {
|
||||||
|
cJSON_AddStringToObject(info, "posting_policy", posting_policy);
|
||||||
|
free((char*)posting_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add server limitations
|
// Add server limitations
|
||||||
if (g_unified_cache.relay_info.limitation) {
|
cJSON* limitation = cJSON_CreateObject();
|
||||||
cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_unified_cache.relay_info.limitation, 1));
|
if (limitation) {
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_message_length", max_message_length);
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_subscriptions", max_subscriptions_per_client);
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_limit", max_limit);
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_event_tags", max_event_tags);
|
||||||
|
cJSON_AddNumberToObject(limitation, "max_content_length", max_content_length);
|
||||||
|
cJSON_AddNumberToObject(limitation, "min_pow_difficulty", min_pow_difficulty);
|
||||||
|
cJSON_AddBoolToObject(limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
||||||
|
cJSON_AddBoolToObject(limitation, "payment_required", cJSON_False);
|
||||||
|
cJSON_AddBoolToObject(limitation, "restricted_writes", cJSON_False);
|
||||||
|
cJSON_AddNumberToObject(limitation, "created_at_lower_limit", 0);
|
||||||
|
cJSON_AddNumberToObject(limitation, "created_at_upper_limit", 2147483647);
|
||||||
|
cJSON_AddNumberToObject(limitation, "default_limit", default_limit);
|
||||||
|
cJSON_AddItemToObject(info, "limitation", limitation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add retention policies if configured
|
// Add retention policies (empty array for now)
|
||||||
if (g_unified_cache.relay_info.retention && cJSON_GetArraySize(g_unified_cache.relay_info.retention) > 0) {
|
cJSON* retention = cJSON_CreateArray();
|
||||||
cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_unified_cache.relay_info.retention, 1));
|
if (retention) {
|
||||||
|
cJSON_AddItemToObject(info, "retention", retention);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add geographical and language information
|
// Add geographical and language information
|
||||||
if (g_unified_cache.relay_info.relay_countries) {
|
if (relay_countries_csv && strlen(relay_countries_csv) > 0) {
|
||||||
cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_unified_cache.relay_info.relay_countries, 1));
|
cJSON* relay_countries = parse_comma_separated_array(relay_countries_csv);
|
||||||
|
if (relay_countries) {
|
||||||
|
cJSON_AddItemToObject(info, "relay_countries", relay_countries);
|
||||||
}
|
}
|
||||||
if (g_unified_cache.relay_info.language_tags) {
|
free((char*)relay_countries_csv);
|
||||||
cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_unified_cache.relay_info.language_tags, 1));
|
} else {
|
||||||
|
cJSON* relay_countries = cJSON_CreateArray();
|
||||||
|
if (relay_countries) {
|
||||||
|
cJSON_AddItemToArray(relay_countries, cJSON_CreateString("*"));
|
||||||
|
cJSON_AddItemToObject(info, "relay_countries", relay_countries);
|
||||||
}
|
}
|
||||||
if (g_unified_cache.relay_info.tags && cJSON_GetArraySize(g_unified_cache.relay_info.tags) > 0) {
|
}
|
||||||
cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_unified_cache.relay_info.tags, 1));
|
|
||||||
|
if (language_tags_csv && strlen(language_tags_csv) > 0) {
|
||||||
|
cJSON* language_tags = parse_comma_separated_array(language_tags_csv);
|
||||||
|
if (language_tags) {
|
||||||
|
cJSON_AddItemToObject(info, "language_tags", language_tags);
|
||||||
|
}
|
||||||
|
free((char*)language_tags_csv);
|
||||||
|
} else {
|
||||||
|
cJSON* language_tags = cJSON_CreateArray();
|
||||||
|
if (language_tags) {
|
||||||
|
cJSON_AddItemToArray(language_tags, cJSON_CreateString("*"));
|
||||||
|
cJSON_AddItemToObject(info, "language_tags", language_tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add content tags (empty array)
|
||||||
|
cJSON* tags = cJSON_CreateArray();
|
||||||
|
if (tags) {
|
||||||
|
cJSON_AddItemToObject(info, "tags", tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add payment information if configured
|
// Add payment information if configured
|
||||||
if (strlen(g_unified_cache.relay_info.payments_url) > 0) {
|
if (payments_url && strlen(payments_url) > 0) {
|
||||||
cJSON_AddStringToObject(info, "payments_url", g_unified_cache.relay_info.payments_url);
|
cJSON_AddStringToObject(info, "payments_url", payments_url);
|
||||||
}
|
free((char*)payments_url);
|
||||||
if (g_unified_cache.relay_info.fees && cJSON_GetObjectItem(g_unified_cache.relay_info.fees, "admission")) {
|
|
||||||
cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_unified_cache.relay_info.fees, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
// Add fees (empty object - no payment required by default)
|
||||||
|
cJSON* fees = cJSON_CreateObject();
|
||||||
|
if (fees) {
|
||||||
|
cJSON_AddItemToObject(info, "fees", fees);
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/nip013.c
75
src/nip013.c
@@ -10,63 +10,38 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
// NIP-13 PoW configuration structure
|
// Configuration functions from config.c
|
||||||
struct pow_config {
|
extern int get_config_bool(const char* key, int default_value);
|
||||||
int enabled; // 0 = disabled, 1 = enabled
|
extern int get_config_int(const char* key, int default_value);
|
||||||
int min_pow_difficulty; // Minimum required difficulty (0 = no requirement)
|
extern const char* get_config_value(const char* key);
|
||||||
int validation_flags; // Bitflags for validation options
|
|
||||||
int require_nonce_tag; // 1 = require nonce tag presence
|
|
||||||
int reject_lower_targets; // 1 = reject if committed < actual difficulty
|
|
||||||
int strict_format; // 1 = enforce strict nonce tag format
|
|
||||||
int anti_spam_mode; // 1 = full anti-spam validation
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize PoW configuration using configuration system
|
// Initialize PoW configuration using configuration system
|
||||||
void init_pow_config() {
|
void init_pow_config() {
|
||||||
|
// Configuration is now handled directly through database queries
|
||||||
// Get all config values first (without holding mutex to avoid deadlock)
|
// No cache initialization needed
|
||||||
int pow_enabled = get_config_bool("pow_enabled", 1);
|
|
||||||
int pow_min_difficulty = get_config_int("pow_min_difficulty", 0);
|
|
||||||
const char* pow_mode = get_config_value("pow_mode");
|
|
||||||
|
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
|
||||||
|
|
||||||
// Load PoW settings from configuration system
|
|
||||||
g_unified_cache.pow_config.enabled = pow_enabled;
|
|
||||||
g_unified_cache.pow_config.min_pow_difficulty = pow_min_difficulty;
|
|
||||||
|
|
||||||
// Configure PoW mode
|
|
||||||
if (pow_mode) {
|
|
||||||
if (strcmp(pow_mode, "strict") == 0) {
|
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
|
||||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
|
||||||
g_unified_cache.pow_config.reject_lower_targets = 1;
|
|
||||||
g_unified_cache.pow_config.strict_format = 1;
|
|
||||||
g_unified_cache.pow_config.anti_spam_mode = 1;
|
|
||||||
} else if (strcmp(pow_mode, "full") == 0) {
|
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
|
||||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
|
||||||
} else if (strcmp(pow_mode, "basic") == 0) {
|
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
|
||||||
} else if (strcmp(pow_mode, "disabled") == 0) {
|
|
||||||
g_unified_cache.pow_config.enabled = 0;
|
|
||||||
}
|
|
||||||
free((char*)pow_mode); // Free dynamically allocated string
|
|
||||||
} else {
|
|
||||||
// Default to basic mode
|
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate event Proof of Work according to NIP-13
|
// Validate event Proof of Work according to NIP-13
|
||||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
// Get PoW configuration directly from database
|
||||||
int enabled = g_unified_cache.pow_config.enabled;
|
int enabled = get_config_bool("pow_enabled", 1);
|
||||||
int min_pow_difficulty = g_unified_cache.pow_config.min_pow_difficulty;
|
int min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||||
int validation_flags = g_unified_cache.pow_config.validation_flags;
|
const char* pow_mode = get_config_value("pow_mode");
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
|
// Determine validation flags based on mode
|
||||||
|
int validation_flags = NOSTR_POW_VALIDATE_BASIC; // Default
|
||||||
|
if (pow_mode) {
|
||||||
|
if (strcmp(pow_mode, "strict") == 0) {
|
||||||
|
validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||||
|
} else if (strcmp(pow_mode, "full") == 0) {
|
||||||
|
validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||||
|
} else if (strcmp(pow_mode, "basic") == 0) {
|
||||||
|
validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||||
|
} else if (strcmp(pow_mode, "disabled") == 0) {
|
||||||
|
enabled = 0;
|
||||||
|
}
|
||||||
|
free((char*)pow_mode);
|
||||||
|
}
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return 0; // PoW validation disabled
|
return 0; // PoW validation disabled
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ extern struct expiration_config {
|
|||||||
// Configuration functions from C-relay
|
// Configuration functions from C-relay
|
||||||
extern int get_config_bool(const char* key, int default_value);
|
extern int get_config_bool(const char* key, int default_value);
|
||||||
extern int get_config_int(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)
|
// NIP-42 constants (from nostr_core_lib)
|
||||||
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242
|
#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
|
// 8. Check if this is a kind 23456 admin event from authorized admin
|
||||||
// This must happen AFTER signature validation but BEFORE auth rules
|
// This must happen AFTER signature validation but BEFORE auth rules
|
||||||
if (event_kind == 23456) {
|
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) {
|
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||||
// Valid admin event - bypass remaining validation
|
// Valid admin event - bypass remaining validation
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
@@ -361,11 +360,9 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
|||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 12. NIP-13 Proof of Work validation
|
// 12. NIP-13 Proof of Work validation
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
int pow_enabled = get_config_bool("pow_enabled", 0);
|
||||||
int pow_enabled = g_unified_cache.pow_config.enabled;
|
int pow_min_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||||
int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty;
|
int pow_validation_flags = get_config_int("pow_validation_flags", 1);
|
||||||
int pow_validation_flags = g_unified_cache.pow_config.validation_flags;
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
|
|
||||||
if (pow_enabled && pow_min_difficulty > 0) {
|
if (pow_enabled && pow_min_difficulty > 0) {
|
||||||
nostr_pow_result_t pow_result;
|
nostr_pow_result_t pow_result;
|
||||||
@@ -506,11 +503,10 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force cache refresh - use unified cache system
|
* Force cache refresh - cache no longer exists, function kept for compatibility
|
||||||
*/
|
*/
|
||||||
void nostr_request_validator_force_cache_refresh(void) {
|
void nostr_request_validator_force_cache_refresh(void) {
|
||||||
// Use unified cache refresh from config.c
|
// Cache no longer exists - direct database queries are used
|
||||||
force_config_cache_refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -180,34 +180,6 @@ BEGIN\n\
|
|||||||
UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\
|
UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\
|
||||||
END;\n\
|
END;\n\
|
||||||
\n\
|
\n\
|
||||||
-- Insert default configuration values\n\
|
|
||||||
INSERT INTO config (key, value, data_type, description, category, requires_restart) VALUES\n\
|
|
||||||
('relay_description', 'A C Nostr Relay', 'string', 'Relay description', 'general', 0),\n\
|
|
||||||
('relay_contact', '', 'string', 'Relay contact information', 'general', 0),\n\
|
|
||||||
('relay_software', 'https://github.com/laanwj/c-relay', 'string', 'Relay software URL', 'general', 0),\n\
|
|
||||||
('relay_version', '1.0.0', 'string', 'Relay version', 'general', 0),\n\
|
|
||||||
('relay_port', '8888', 'integer', 'Relay port number', 'network', 1),\n\
|
|
||||||
('max_connections', '1000', 'integer', 'Maximum concurrent connections', 'network', 1),\n\
|
|
||||||
('auth_enabled', 'false', 'boolean', 'Enable NIP-42 authentication', 'auth', 0),\n\
|
|
||||||
('nip42_auth_required_events', 'false', 'boolean', 'Require auth for event publishing', 'auth', 0),\n\
|
|
||||||
('nip42_auth_required_subscriptions', 'false', 'boolean', 'Require auth for subscriptions', 'auth', 0),\n\
|
|
||||||
('nip42_auth_required_kinds', '[]', 'json', 'Event kinds requiring authentication', 'auth', 0),\n\
|
|
||||||
('nip42_challenge_expiration', '600', 'integer', 'Auth challenge expiration seconds', 'auth', 0),\n\
|
|
||||||
('pow_min_difficulty', '0', 'integer', 'Minimum proof-of-work difficulty', 'validation', 0),\n\
|
|
||||||
('pow_mode', 'optional', 'string', 'Proof-of-work mode', 'validation', 0),\n\
|
|
||||||
('nip40_expiration_enabled', 'true', 'boolean', 'Enable event expiration', 'validation', 0),\n\
|
|
||||||
('nip40_expiration_strict', 'false', 'boolean', 'Strict expiration mode', 'validation', 0),\n\
|
|
||||||
('nip40_expiration_filter', 'true', 'boolean', 'Filter expired events in queries', 'validation', 0),\n\
|
|
||||||
('nip40_expiration_grace_period', '60', 'integer', 'Expiration grace period seconds', 'validation', 0),\n\
|
|
||||||
('max_subscriptions_per_client', '25', 'integer', 'Maximum subscriptions per client', 'limits', 0),\n\
|
|
||||||
('max_total_subscriptions', '1000', 'integer', 'Maximum total subscriptions', 'limits', 0),\n\
|
|
||||||
('max_filters_per_subscription', '10', 'integer', 'Maximum filters per subscription', 'limits', 0),\n\
|
|
||||||
('max_event_tags', '2000', 'integer', 'Maximum tags per event', 'limits', 0),\n\
|
|
||||||
('max_content_length', '100000', 'integer', 'Maximum event content length', 'limits', 0),\n\
|
|
||||||
('max_message_length', '131072', 'integer', 'Maximum WebSocket message length', 'limits', 0),\n\
|
|
||||||
('default_limit', '100', 'integer', 'Default query limit', 'limits', 0),\n\
|
|
||||||
('max_limit', '5000', 'integer', 'Maximum query limit', 'limits', 0);\n\
|
|
||||||
\n\
|
|
||||||
-- Persistent Subscriptions Logging Tables (Phase 2)\n\
|
-- Persistent Subscriptions Logging Tables (Phase 2)\n\
|
||||||
-- Optional database logging for subscription analytics and debugging\n\
|
-- Optional database logging for subscription analytics and debugging\n\
|
||||||
\n\
|
\n\
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ int validate_search_term(const char* search_term, char* error_message, size_t er
|
|||||||
// Global database variable
|
// Global database variable
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
|
|
||||||
// Global unified cache
|
// Configuration functions from config.c
|
||||||
extern unified_config_cache_t g_unified_cache;
|
extern int get_config_bool(const char* key, int default_value);
|
||||||
|
|
||||||
// Global subscription manager
|
// Global subscription manager
|
||||||
extern subscription_manager_t g_subscription_manager;
|
extern subscription_manager_t g_subscription_manager;
|
||||||
@@ -534,10 +534,8 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if event is expired and should not be broadcast (NIP-40)
|
// Check if event is expired and should not be broadcast (NIP-40)
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||||
int expiration_enabled = g_unified_cache.expiration_config.enabled;
|
int filter_responses = get_config_bool("expiration_filter", 1);
|
||||||
int filter_responses = g_unified_cache.expiration_config.filter_responses;
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
|
||||||
|
|
||||||
if (expiration_enabled && filter_responses) {
|
if (expiration_enabled && filter_responses) {
|
||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size
|
|||||||
// Forward declarations for NOTICE message support
|
// Forward declarations for NOTICE message support
|
||||||
void send_notice_message(struct lws* wsi, const char* message);
|
void send_notice_message(struct lws* wsi, const char* message);
|
||||||
|
|
||||||
// Forward declarations for unified cache access
|
// Configuration functions from config.c
|
||||||
extern unified_config_cache_t g_unified_cache;
|
extern int get_config_bool(const char* key, int default_value);
|
||||||
|
|
||||||
// Forward declarations for global state
|
// Forward declarations for global state
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
@@ -311,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)) {
|
if (event_kind == 14 && event_obj && cJSON_IsObject(event_obj)) {
|
||||||
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
|
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
|
||||||
if (tags && cJSON_IsArray(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) {
|
if (relay_pubkey) {
|
||||||
cJSON* tag = NULL;
|
cJSON* tag = NULL;
|
||||||
cJSON_ArrayForEach(tag, tags) {
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
@@ -333,7 +333,7 @@ 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
|
// Special case: allow kind 23456 admin events from authorized admin to bypass auth
|
||||||
if (event_kind == 23456 && event_pubkey) {
|
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) {
|
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||||
bypass_auth = 1;
|
bypass_auth = 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -453,8 +453,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_protected_event) {
|
if (is_protected_event) {
|
||||||
// Check if protected events are enabled using unified cache
|
// Check if protected events are enabled using config
|
||||||
int protected_events_enabled = g_unified_cache.nip70_protected_events_enabled;
|
int protected_events_enabled = get_config_bool("nip70_protected_events_enabled", 0);
|
||||||
|
|
||||||
if (!protected_events_enabled) {
|
if (!protected_events_enabled) {
|
||||||
// Protected events not supported
|
// Protected events not supported
|
||||||
@@ -1159,7 +1159,7 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
if (!relay_pubkey) {
|
if (!relay_pubkey) {
|
||||||
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
|
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1197,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);
|
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||||
|
|
||||||
// Check if sender is admin
|
// 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 ||
|
if (!admin_pubkey || strlen(admin_pubkey) == 0 ||
|
||||||
strcmp(sender_pubkey, admin_pubkey) != 0) {
|
strcmp(sender_pubkey, admin_pubkey) != 0) {
|
||||||
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
|
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user