v0.7.13 - -t
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<label for="relay-connection-url">Relay URL:</label>
|
||||
<input type="text" id="relay-connection-url" value="ws://localhost:8888"
|
||||
<input type="text" id="relay-connection-url" value=""
|
||||
placeholder="ws://localhost:8888 or wss://relay.example.com">
|
||||
</div>
|
||||
|
||||
|
||||
28
api/index.js
28
api/index.js
@@ -3497,10 +3497,38 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Set default relay URL based on where the page is being served from
|
||||
function setDefaultRelayUrl() {
|
||||
const relayUrlInput = document.getElementById('relay-connection-url');
|
||||
if (!relayUrlInput) return;
|
||||
|
||||
// Get the current page's protocol and hostname
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const hostname = window.location.hostname;
|
||||
const port = window.location.port;
|
||||
|
||||
// Construct the relay URL
|
||||
let relayUrl;
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||||
// For localhost, default to ws://localhost:8888
|
||||
relayUrl = 'ws://localhost:8888';
|
||||
} else {
|
||||
// For production, use the same hostname with WebSocket protocol
|
||||
// Remove port from URL since relay typically runs on standard ports (80/443)
|
||||
relayUrl = `${protocol}//${hostname}`;
|
||||
}
|
||||
|
||||
relayUrlInput.value = relayUrl;
|
||||
log(`Default relay URL set to: ${relayUrl}`, 'INFO');
|
||||
}
|
||||
|
||||
// Initialize the app
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('C-Relay Admin API interface loaded');
|
||||
|
||||
// Set default relay URL based on current page location
|
||||
setDefaultRelayUrl();
|
||||
|
||||
// Initialize login/logout button state
|
||||
updateLoginLogoutButton();
|
||||
|
||||
|
||||
@@ -238,6 +238,162 @@ DEBUG_TRACE("Entering handle_req_message()");
|
||||
DEBUG_TRACE("Subscription ID validated: %s", sub_id);
|
||||
DEBUG_TRACE("Exiting handle_req_message()");
|
||||
```
|
||||
## Manual Guards for Expensive Operations
|
||||
|
||||
### The Problem
|
||||
|
||||
Debug macros use **runtime checks**, which means function arguments are always evaluated:
|
||||
|
||||
```c
|
||||
// ❌ BAD: Database query executes even when debug level is 0
|
||||
DEBUG_LOG("Count: %d", expensive_database_query());
|
||||
```
|
||||
|
||||
The `expensive_database_query()` will **always execute** because function arguments are evaluated before the `if` check inside the macro.
|
||||
|
||||
### The Solution: Manual Guards
|
||||
|
||||
For expensive operations (database queries, file I/O, complex calculations), use manual guards:
|
||||
|
||||
```c
|
||||
// ✅ GOOD: Query only executes when debugging is enabled
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
int count = expensive_database_query();
|
||||
DEBUG_LOG("Count: %d", count);
|
||||
}
|
||||
```
|
||||
|
||||
### Standardized Comment Format
|
||||
|
||||
To make temporary debug guards easy to find and remove, use this standardized format:
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
// Expensive operation here
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT COUNT(*) FROM events";
|
||||
int count = 0;
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
count = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
DEBUG_LOG("Event count: %d", count);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Easy Removal
|
||||
|
||||
When you're done debugging, find and remove all temporary guards:
|
||||
|
||||
```bash
|
||||
# Find all debug guards
|
||||
grep -n "DEBUG_GUARD_START" src/*.c
|
||||
|
||||
# Remove guards with sed (between START and END markers)
|
||||
sed -i '/DEBUG_GUARD_START/,/DEBUG_GUARD_END/d' src/config.c
|
||||
```
|
||||
|
||||
Or use a simple script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# remove_debug_guards.sh
|
||||
for file in src/*.c; do
|
||||
sed -i '/DEBUG_GUARD_START/,/DEBUG_GUARD_END/d' "$file"
|
||||
echo "Removed debug guards from $file"
|
||||
done
|
||||
```
|
||||
|
||||
### When to Use Manual Guards
|
||||
|
||||
Use manual guards for:
|
||||
- ✅ Database queries
|
||||
- ✅ File I/O operations
|
||||
- ✅ Network requests
|
||||
- ✅ Complex calculations
|
||||
- ✅ Memory allocations for debug data
|
||||
- ✅ String formatting with multiple operations
|
||||
|
||||
Don't need guards for:
|
||||
- ❌ Simple variable access
|
||||
- ❌ Basic arithmetic
|
||||
- ❌ String literals
|
||||
- ❌ Function calls that are already cheap
|
||||
|
||||
### Example: Database Query Guard
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* count_stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
int config_count = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(count_stmt) == SQLITE_ROW) {
|
||||
config_count = sqlite3_column_int(count_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(count_stmt);
|
||||
}
|
||||
|
||||
DEBUG_LOG("Config table has %d rows", config_count);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Example: Complex String Formatting Guard
|
||||
|
||||
```c
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_TRACE) {
|
||||
char filter_str[1024] = {0};
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < filter_count && offset < sizeof(filter_str) - 1; i++) {
|
||||
offset += snprintf(filter_str + offset, sizeof(filter_str) - offset,
|
||||
"Filter %d: kind=%d, author=%s; ",
|
||||
i, filters[i].kind, filters[i].author);
|
||||
}
|
||||
|
||||
DEBUG_TRACE("Processing filters: %s", filter_str);
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
```
|
||||
|
||||
### Alternative: Compile-Time Guards
|
||||
|
||||
For permanent debug code that should be completely removed in production builds, use compile-time guards:
|
||||
|
||||
```c
|
||||
#ifdef ENABLE_DEBUG_CODE
|
||||
// This code is completely removed when ENABLE_DEBUG_CODE is not defined
|
||||
int count = expensive_database_query();
|
||||
DEBUG_LOG("Count: %d", count);
|
||||
#endif
|
||||
```
|
||||
|
||||
Build with debug code:
|
||||
```bash
|
||||
make CFLAGS="-DENABLE_DEBUG_CODE"
|
||||
```
|
||||
|
||||
Build without debug code (production):
|
||||
```bash
|
||||
make # No debug code compiled in
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always use standardized markers** (`DEBUG_GUARD_START`/`DEBUG_GUARD_END`) for temporary guards
|
||||
2. **Add a comment** explaining what you're debugging
|
||||
3. **Remove guards** when debugging is complete
|
||||
4. **Use compile-time guards** for permanent debug infrastructure
|
||||
5. **Keep guards simple** - one guard per logical debug operation
|
||||
|
||||
|
||||
## Performance Impact
|
||||
|
||||
|
||||
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
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
echo "Using deterministic test keys for development..."
|
||||
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
elif [ -n "$RELAY_ARGS" ]; then
|
||||
echo "Starting relay with custom configuration..."
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
else
|
||||
# No command line arguments needed for random key generation
|
||||
echo "Starting relay with random key generation..."
|
||||
./$(basename $BINARY_PATH) --strict-port > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) --debug-level=$DEBUG_LEVEL --strict-port > ../relay.log 2>&1 &
|
||||
fi
|
||||
RELAY_PID=$!
|
||||
# Change back to original directory
|
||||
|
||||
563
src/config.c
563
src/config.c
@@ -63,6 +63,7 @@ typedef enum {
|
||||
|
||||
// Forward declarations for new admin API functions
|
||||
int populate_default_config_values(void);
|
||||
int populate_all_config_values_atomic(const char* admin_pubkey, const char* relay_pubkey);
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||
void invalidate_config_cache(void);
|
||||
|
||||
@@ -220,6 +221,8 @@ static int refresh_unified_cache_from_table(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_LOG("Starting unified cache refresh from database");
|
||||
|
||||
// Log config table row count at start of refresh_unified_cache_from_table
|
||||
sqlite3_stmt* count_stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
@@ -236,30 +239,50 @@ static int refresh_unified_cache_from_table(void) {
|
||||
|
||||
// Load critical config values from table
|
||||
const char* admin_pubkey = get_config_value_from_table("admin_pubkey");
|
||||
DEBUG_TRACE("get_config_value_from_table('admin_pubkey') returned: %s",
|
||||
admin_pubkey ? admin_pubkey : "NULL");
|
||||
DEBUG_LOG("Loading admin_pubkey from config table: %s", admin_pubkey ? admin_pubkey : "NULL");
|
||||
if (admin_pubkey) {
|
||||
DEBUG_LOG("Setting admin_pubkey in cache: %s", admin_pubkey);
|
||||
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
DEBUG_TRACE("Set admin_pubkey in cache: %s", g_unified_cache.admin_pubkey);
|
||||
free((char*)admin_pubkey);
|
||||
} else {
|
||||
DEBUG_LOG("No admin_pubkey found in config table");
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
|
||||
DEBUG_TRACE("get_config_value_from_table('relay_pubkey') returned: %s",
|
||||
relay_pubkey ? relay_pubkey : "NULL");
|
||||
DEBUG_LOG("Loading relay_pubkey from config table: %s", relay_pubkey ? relay_pubkey : "NULL");
|
||||
if (relay_pubkey) {
|
||||
DEBUG_LOG("Setting relay_pubkey in cache: %s", relay_pubkey);
|
||||
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||
DEBUG_TRACE("Set relay_pubkey in cache: %s", g_unified_cache.relay_pubkey);
|
||||
free((char*)relay_pubkey);
|
||||
} else {
|
||||
DEBUG_LOG("No relay_pubkey found in config table");
|
||||
}
|
||||
|
||||
// Load auth-related config
|
||||
const char* auth_required = get_config_value_from_table("auth_required");
|
||||
g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0;
|
||||
DEBUG_TRACE("Loaded auth_required from table: %s (cache value: %d)",
|
||||
auth_required ? auth_required : "NULL", g_unified_cache.auth_required);
|
||||
if (auth_required) free((char*)auth_required);
|
||||
|
||||
const char* admin_enabled = get_config_value_from_table("admin_enabled");
|
||||
g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0;
|
||||
DEBUG_TRACE("Loaded admin_enabled from table: %s (cache value: %d)",
|
||||
admin_enabled ? admin_enabled : "NULL", g_unified_cache.admin_enabled);
|
||||
if (admin_enabled) free((char*)admin_enabled);
|
||||
|
||||
const char* max_file_size = get_config_value_from_table("max_file_size");
|
||||
g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default
|
||||
DEBUG_TRACE("Loaded max_file_size from table: %s (cache value: %ld)",
|
||||
max_file_size ? max_file_size : "NULL", g_unified_cache.max_file_size);
|
||||
if (max_file_size) free((char*)max_file_size);
|
||||
|
||||
const char* nip42_mode = get_config_value_from_table("nip42_mode");
|
||||
@@ -271,6 +294,8 @@ static int refresh_unified_cache_from_table(void) {
|
||||
} else {
|
||||
g_unified_cache.nip42_mode = 1; // Optional/enabled
|
||||
}
|
||||
DEBUG_TRACE("Loaded nip42_mode from table: %s (cache value: %d)",
|
||||
nip42_mode, g_unified_cache.nip42_mode);
|
||||
free((char*)nip42_mode);
|
||||
} else {
|
||||
g_unified_cache.nip42_mode = 1; // Default to optional/enabled
|
||||
@@ -287,6 +312,8 @@ static int refresh_unified_cache_from_table(void) {
|
||||
// Load NIP-70 protected events config
|
||||
const char* nip70_enabled = get_config_value_from_table("nip70_protected_events_enabled");
|
||||
g_unified_cache.nip70_protected_events_enabled = (nip70_enabled && strcmp(nip70_enabled, "true") == 0) ? 1 : 0;
|
||||
DEBUG_TRACE("Loaded nip70_protected_events_enabled from table: %s (cache value: %d)",
|
||||
nip70_enabled ? nip70_enabled : "NULL", g_unified_cache.nip70_protected_events_enabled);
|
||||
if (nip70_enabled) free((char*)nip70_enabled);
|
||||
|
||||
// Load NIP-11 relay info fields directly into cache (avoid circular dependency)
|
||||
@@ -368,6 +395,9 @@ static int refresh_unified_cache_from_table(void) {
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
DEBUG_LOG("Configuration cache refreshed (%d entries)", config_count);
|
||||
DEBUG_LOG("Final cache state - admin_pubkey: %s, relay_pubkey: %s",
|
||||
g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : "EMPTY",
|
||||
g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : "EMPTY");
|
||||
|
||||
// Log config table row count at end of refresh_unified_cache_from_table
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) {
|
||||
@@ -381,44 +411,131 @@ static int refresh_unified_cache_from_table(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get admin pubkey from cache (with automatic refresh)
|
||||
const char* get_admin_pubkey_cached(void) {
|
||||
// First check without holding lock: whether we need refresh
|
||||
|
||||
// Unified cache getter function - handles all config values through unified cache
|
||||
const char* get_cached_config_value(const char* key) {
|
||||
if (!key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check cache validity and refresh if needed
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (need_refresh) {
|
||||
// Perform refresh, which locks internally
|
||||
DEBUG_TRACE("Cache refresh needed for key '%s', calling refresh_unified_cache_from_table()", key);
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
// Now read under lock
|
||||
// Return value from cache based on key
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
const char* result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
|
||||
|
||||
const char* result = NULL;
|
||||
|
||||
// Handle string fields from unified cache
|
||||
if (strcmp(key, "admin_pubkey") == 0) {
|
||||
result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
|
||||
DEBUG_TRACE("Returning admin_pubkey from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_pubkey") == 0) {
|
||||
result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
|
||||
DEBUG_TRACE("Returning relay_pubkey from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_name") == 0) {
|
||||
result = g_unified_cache.relay_info.name[0] ? g_unified_cache.relay_info.name : NULL;
|
||||
DEBUG_TRACE("Returning relay_name from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_description") == 0) {
|
||||
result = g_unified_cache.relay_info.description[0] ? g_unified_cache.relay_info.description : NULL;
|
||||
DEBUG_TRACE("Returning relay_description from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_contact") == 0) {
|
||||
result = g_unified_cache.relay_info.contact[0] ? g_unified_cache.relay_info.contact : NULL;
|
||||
DEBUG_TRACE("Returning relay_contact from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_software") == 0) {
|
||||
result = g_unified_cache.relay_info.software[0] ? g_unified_cache.relay_info.software : NULL;
|
||||
DEBUG_TRACE("Returning relay_software from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_version") == 0) {
|
||||
result = g_unified_cache.relay_info.version[0] ? g_unified_cache.relay_info.version : NULL;
|
||||
DEBUG_TRACE("Returning relay_version from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "supported_nips") == 0) {
|
||||
result = g_unified_cache.relay_info.supported_nips_str[0] ? g_unified_cache.relay_info.supported_nips_str : NULL;
|
||||
DEBUG_TRACE("Returning supported_nips from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "language_tags") == 0) {
|
||||
result = g_unified_cache.relay_info.language_tags_str[0] ? g_unified_cache.relay_info.language_tags_str : NULL;
|
||||
DEBUG_TRACE("Returning language_tags from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "relay_countries") == 0) {
|
||||
result = g_unified_cache.relay_info.relay_countries_str[0] ? g_unified_cache.relay_info.relay_countries_str : NULL;
|
||||
DEBUG_TRACE("Returning relay_countries from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "posting_policy") == 0) {
|
||||
result = g_unified_cache.relay_info.posting_policy[0] ? g_unified_cache.relay_info.posting_policy : NULL;
|
||||
DEBUG_TRACE("Returning posting_policy from cache: %s", result ? result : "NULL");
|
||||
} else if (strcmp(key, "payments_url") == 0) {
|
||||
result = g_unified_cache.relay_info.payments_url[0] ? g_unified_cache.relay_info.payments_url : NULL;
|
||||
DEBUG_TRACE("Returning payments_url from cache: %s", result ? result : "NULL");
|
||||
} else {
|
||||
DEBUG_TRACE("Key '%s' not found in unified cache", key);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get relay pubkey from cache (with automatic refresh)
|
||||
const char* get_relay_pubkey_cached(void) {
|
||||
// First check without holding lock: whether we need refresh
|
||||
// Get integer config value from unified cache
|
||||
int get_cached_config_int(const char* key, int default_value) {
|
||||
if (!key) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
// Check cache validity and refresh if needed
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (need_refresh) {
|
||||
// Perform refresh, which locks internally
|
||||
DEBUG_TRACE("Cache refresh needed for int key '%s'", key);
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
// Now read under lock
|
||||
// Return value from cache based on key
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
const char* result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
|
||||
|
||||
int result = default_value;
|
||||
|
||||
// Handle integer fields from unified cache
|
||||
if (strcmp(key, "auth_required") == 0) {
|
||||
result = g_unified_cache.auth_required;
|
||||
DEBUG_TRACE("Returning auth_required from cache: %d", result);
|
||||
} else if (strcmp(key, "admin_enabled") == 0) {
|
||||
result = g_unified_cache.admin_enabled;
|
||||
DEBUG_TRACE("Returning admin_enabled from cache: %d", result);
|
||||
} else if (strcmp(key, "max_file_size") == 0) {
|
||||
result = g_unified_cache.max_file_size;
|
||||
DEBUG_TRACE("Returning max_file_size from cache: %d", result);
|
||||
} else if (strcmp(key, "nip42_mode") == 0) {
|
||||
result = g_unified_cache.nip42_mode;
|
||||
DEBUG_TRACE("Returning nip42_mode from cache: %d", result);
|
||||
} else if (strcmp(key, "nip42_challenge_timeout") == 0) {
|
||||
result = g_unified_cache.nip42_challenge_timeout;
|
||||
DEBUG_TRACE("Returning nip42_challenge_timeout from cache: %d", result);
|
||||
} else if (strcmp(key, "nip42_time_tolerance") == 0) {
|
||||
result = g_unified_cache.nip42_time_tolerance;
|
||||
DEBUG_TRACE("Returning nip42_time_tolerance from cache: %d", result);
|
||||
} else if (strcmp(key, "nip70_protected_events_enabled") == 0) {
|
||||
result = g_unified_cache.nip70_protected_events_enabled;
|
||||
DEBUG_TRACE("Returning nip70_protected_events_enabled from cache: %d", result);
|
||||
} else {
|
||||
DEBUG_TRACE("Integer key '%s' not found in unified cache, using default: %d", key, default_value);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get boolean config value from unified cache
|
||||
int get_cached_config_bool(const char* key, int default_value) {
|
||||
// Boolean values are stored as integers (0/1) in the cache
|
||||
return get_cached_config_int(key, default_value);
|
||||
}
|
||||
|
||||
|
||||
// ================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ================================
|
||||
@@ -606,64 +723,34 @@ const char* get_config_value(const char* key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Special fast path for frequently accessed keys via unified cache
|
||||
if (strcmp(key, "admin_pubkey") == 0) {
|
||||
const char* cached_value = get_admin_pubkey_cached();
|
||||
return safe_strdup_from_static(cached_value);
|
||||
}
|
||||
if (strcmp(key, "relay_pubkey") == 0) {
|
||||
const char* cached_value = get_relay_pubkey_cached();
|
||||
// Try unified cache first for all supported keys
|
||||
const char* cached_value = get_cached_config_value(key);
|
||||
if (cached_value) {
|
||||
return safe_strdup_from_static(cached_value);
|
||||
}
|
||||
|
||||
// For other keys, try config table first
|
||||
// For other keys, try config table directly
|
||||
const char* table_value = get_config_value_from_table(key);
|
||||
if (table_value) {
|
||||
return table_value;
|
||||
}
|
||||
|
||||
// Fallback to legacy event-based config for backward compatibility
|
||||
// Use unified cache buffer instead of static buffer
|
||||
|
||||
if (!g_current_config) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Look for key in current configuration tags
|
||||
cJSON* tags = cJSON_GetObjectItem(g_current_config, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
||||
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (tag_key && tag_value &&
|
||||
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
|
||||
|
||||
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
|
||||
strncpy(g_unified_cache.temp_buffer, cJSON_GetStringValue(tag_value),
|
||||
sizeof(g_unified_cache.temp_buffer) - 1);
|
||||
g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0';
|
||||
const char* result = safe_strdup_from_static(g_unified_cache.temp_buffer);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int get_config_int(const char* key, int default_value) {
|
||||
// Try unified cache first for integer values
|
||||
if (strcmp(key, "auth_required") == 0 ||
|
||||
strcmp(key, "admin_enabled") == 0 ||
|
||||
strcmp(key, "max_file_size") == 0 ||
|
||||
strcmp(key, "nip42_mode") == 0 ||
|
||||
strcmp(key, "nip42_challenge_timeout") == 0 ||
|
||||
strcmp(key, "nip42_time_tolerance") == 0 ||
|
||||
strcmp(key, "nip70_protected_events_enabled") == 0) {
|
||||
return get_cached_config_int(key, default_value);
|
||||
}
|
||||
|
||||
// Fall back to string parsing for other keys
|
||||
const char* str_value = get_config_value(key);
|
||||
if (!str_value) {
|
||||
return default_value;
|
||||
@@ -684,6 +771,14 @@ int get_config_int(const char* key, int default_value) {
|
||||
}
|
||||
|
||||
int get_config_bool(const char* key, int default_value) {
|
||||
// Try unified cache first for boolean values
|
||||
if (strcmp(key, "auth_required") == 0 ||
|
||||
strcmp(key, "admin_enabled") == 0 ||
|
||||
strcmp(key, "nip70_protected_events_enabled") == 0) {
|
||||
return get_cached_config_bool(key, default_value);
|
||||
}
|
||||
|
||||
// Fall back to string parsing for other keys
|
||||
const char* str_value = get_config_value(key);
|
||||
if (!str_value) {
|
||||
return default_value;
|
||||
@@ -1258,13 +1353,23 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
||||
strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1);
|
||||
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
|
||||
|
||||
// 6. Handle configuration setup - defaults will be populated after database initialization
|
||||
// 6. Populate complete config table with all values atomically
|
||||
if (populate_all_config_values_atomic(admin_pubkey, relay_pubkey) != 0) {
|
||||
DEBUG_ERROR("Failed to populate complete config table");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 7. Apply CLI overrides atomically (after complete config table exists)
|
||||
if (apply_cli_overrides_atomic(cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to apply CLI overrides");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// CLI overrides will be applied after database initialization in main.c
|
||||
// This prevents "g_db is NULL" errors during first-time startup
|
||||
// 8. Store relay private key in temporary storage for later secure storage
|
||||
strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1);
|
||||
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
|
||||
|
||||
// 10. Print admin private key for user to save (only if we generated a new key)
|
||||
// 9. Print admin private key for user to save (only if we generated a new key)
|
||||
if (generated_admin_key) {
|
||||
printf("\n");
|
||||
printf("=================================================================\n");
|
||||
@@ -1294,7 +1399,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int startup_existing_relay(const char* relay_pubkey) {
|
||||
int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_options) {
|
||||
if (!relay_pubkey) {
|
||||
DEBUG_ERROR("Invalid relay pubkey for existing relay startup");
|
||||
return -1;
|
||||
@@ -1302,28 +1407,16 @@ int startup_existing_relay(const char* relay_pubkey) {
|
||||
|
||||
printf(" Relay pubkey: %s\n", relay_pubkey);
|
||||
|
||||
// Log config table row count at start of startup_existing_relay
|
||||
if (g_db) {
|
||||
sqlite3_stmt* count_stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(count_stmt) == SQLITE_ROW) {
|
||||
// Row count check completed
|
||||
}
|
||||
sqlite3_finalize(count_stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Store relay pubkey in unified cache
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||
|
||||
|
||||
// Mark cache as valid to prevent premature refresh from database before it's initialized
|
||||
int cache_timeout = get_cache_timeout();
|
||||
g_unified_cache.cache_expires = time(NULL) + cache_timeout;
|
||||
g_unified_cache.cache_valid = 1;
|
||||
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Set database path
|
||||
@@ -1337,19 +1430,28 @@ int startup_existing_relay(const char* relay_pubkey) {
|
||||
g_database_path[sizeof(g_database_path) - 1] = '\0';
|
||||
free(db_name);
|
||||
|
||||
// NOTE: admin_pubkey will be loaded from config table after database initialization
|
||||
// in main.c by calling add_pubkeys_to_config_table() which handles migration
|
||||
// NOTE: Database is already initialized in main.c before calling this function
|
||||
// Config table should already exist with complete configuration
|
||||
|
||||
// Log config table row count at end of startup_existing_relay
|
||||
if (g_db) {
|
||||
sqlite3_stmt* count_stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(count_stmt) == SQLITE_ROW) {
|
||||
// Row count check completed
|
||||
}
|
||||
sqlite3_finalize(count_stmt);
|
||||
// Check if CLI overrides need to be applied
|
||||
int has_overrides = 0;
|
||||
if (cli_options) {
|
||||
if (cli_options->port_override > 0) has_overrides = 1;
|
||||
if (cli_options->admin_pubkey_override[0] != '\0') has_overrides = 1;
|
||||
if (cli_options->relay_privkey_override[0] != '\0') has_overrides = 1;
|
||||
}
|
||||
|
||||
if (has_overrides) {
|
||||
// Apply CLI overrides to existing database
|
||||
DEBUG_INFO("Applying CLI overrides to existing database");
|
||||
if (apply_cli_overrides_atomic(cli_options) != 0) {
|
||||
DEBUG_ERROR("Failed to apply CLI overrides to existing database");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// No CLI overrides - load cache immediately from existing config table
|
||||
DEBUG_INFO("No CLI overrides - loading cache immediately from existing config table");
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1829,7 +1931,7 @@ int process_configuration_event(const cJSON* event) {
|
||||
|
||||
// Verify it's from the admin
|
||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
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 (strcmp(event_pubkey, admin_pubkey) != 0) {
|
||||
DEBUG_ERROR("Configuration event not from authorized admin");
|
||||
@@ -2003,7 +2105,7 @@ int apply_configuration_from_event(const cJSON* event) {
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (pubkey_obj) {
|
||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
const char* cached_admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* cached_admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!cached_admin_pubkey || strlen(cached_admin_pubkey) == 0) {
|
||||
// Update cache with admin pubkey from event
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
@@ -2154,7 +2256,9 @@ int update_config_in_table(const char* key, const char* value) {
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Populate default config values (only inserts missing keys, doesn't replace existing)
|
||||
// DEPRECATED: This function is no longer used in the unified startup flow.
|
||||
// The new populate_all_config_values_atomic() function handles all config creation atomically.
|
||||
// This function is kept for backward compatibility but should not be called in new code.
|
||||
int populate_default_config_values(void) {
|
||||
DEBUG_TRACE("Entering populate_default_config_values()");
|
||||
|
||||
@@ -2318,7 +2422,9 @@ int populate_default_config_values(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add dynamically generated pubkeys to config table
|
||||
// DEPRECATED: This function is no longer used in the unified startup flow.
|
||||
// The new populate_all_config_values_atomic() function handles pubkey storage atomically.
|
||||
// This function is kept for backward compatibility but should not be called in new code.
|
||||
int add_pubkeys_to_config_table(void) {
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for pubkey storage");
|
||||
@@ -2345,67 +2451,73 @@ int add_pubkeys_to_config_table(void) {
|
||||
|
||||
// For existing relays, admin_pubkey might already be in the database but not in cache yet
|
||||
// Try to load it from the database config table first
|
||||
const char* admin_pubkey_from_db = NULL;
|
||||
if (!admin_pubkey_cache || strlen(admin_pubkey_cache) != 64) {
|
||||
admin_pubkey_from_db = get_config_value_from_table("admin_pubkey");
|
||||
if (admin_pubkey_from_db && strlen(admin_pubkey_from_db) == 64) {
|
||||
// Update cache with the value from config table
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.admin_pubkey, admin_pubkey_from_db, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
DEBUG_INFO("✓ Loaded admin_pubkey from config table into cache");
|
||||
// Free the allocated string and return success - pubkey already in table
|
||||
free((char*)admin_pubkey_from_db);
|
||||
return 0;
|
||||
}
|
||||
if (admin_pubkey_from_db) {
|
||||
free((char*)admin_pubkey_from_db);
|
||||
}
|
||||
DEBUG_LOG("Checking for existing admin_pubkey in config table");
|
||||
const char* admin_pubkey_from_db = get_config_value_from_table("admin_pubkey");
|
||||
DEBUG_TRACE("get_config_value_from_table('admin_pubkey') returned: %s (length: %zu)",
|
||||
admin_pubkey_from_db ? admin_pubkey_from_db : "NULL",
|
||||
admin_pubkey_from_db ? strlen(admin_pubkey_from_db) : 0);
|
||||
DEBUG_LOG("get_config_value_from_table returned: %s", admin_pubkey_from_db ? admin_pubkey_from_db : "NULL");
|
||||
if (admin_pubkey_from_db && strlen(admin_pubkey_from_db) == 64) {
|
||||
DEBUG_LOG("Found valid admin_pubkey in config table: %s", admin_pubkey_from_db);
|
||||
// Update cache with the value from config table
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.admin_pubkey, admin_pubkey_from_db, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
DEBUG_TRACE("Loaded admin_pubkey from config table into cache: %s", g_unified_cache.admin_pubkey);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
DEBUG_INFO("✓ Loaded admin_pubkey from config table into cache");
|
||||
// Free the allocated string and return success - pubkey already in table
|
||||
free((char*)admin_pubkey_from_db);
|
||||
return 0;
|
||||
} else {
|
||||
DEBUG_LOG("No valid admin_pubkey found in config table (length: %zu)", admin_pubkey_from_db ? strlen(admin_pubkey_from_db) : 0);
|
||||
}
|
||||
if (admin_pubkey_from_db) {
|
||||
free((char*)admin_pubkey_from_db);
|
||||
}
|
||||
|
||||
// If not in config table, try loading from old event-based config (migration scenario)
|
||||
const char* sql = "SELECT pubkey FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (event_pubkey && strlen(event_pubkey) == 64) {
|
||||
// Store in config table for future use
|
||||
if (set_config_value_in_table("admin_pubkey", event_pubkey, "string",
|
||||
"Administrator public key", "authentication", 0) == 0) {
|
||||
// If not in config table, try loading from old event-based config (migration scenario)
|
||||
const char* sql = "SELECT pubkey FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
// Update cache
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (event_pubkey && strlen(event_pubkey) == 64) {
|
||||
// Store in config table for future use
|
||||
if (set_config_value_in_table("admin_pubkey", event_pubkey, "string",
|
||||
"Administrator public key", "authentication", 0) == 0) {
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
DEBUG_INFO("✓ Migrated admin_pubkey from old config event to config table");
|
||||
return 0;
|
||||
}
|
||||
// Update cache
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
DEBUG_INFO("✓ Migrated admin_pubkey from old config event to config table");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
|
||||
// Use cache value for storage (either from first-time startup or just loaded from DB)
|
||||
const char* admin_pubkey = admin_pubkey_cache;
|
||||
const char* relay_pubkey = relay_pubkey_cache;
|
||||
|
||||
|
||||
if (!admin_pubkey || strlen(admin_pubkey) != 64) {
|
||||
DEBUG_ERROR("Admin pubkey not available or invalid for config table storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||
DEBUG_ERROR("Relay pubkey not available or invalid for config table storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Store admin pubkey in config table
|
||||
if (set_config_value_in_table("admin_pubkey", admin_pubkey, "string",
|
||||
"Administrator public key", "authentication", 0) != 0) {
|
||||
@@ -2419,7 +2531,7 @@ int add_pubkeys_to_config_table(void) {
|
||||
DEBUG_ERROR("Failed to store relay_pubkey in config table");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
DEBUG_INFO("✓ Dynamically generated pubkeys added to config table");
|
||||
printf(" Admin pubkey: %s\n", admin_pubkey ? admin_pubkey : "NULL");
|
||||
printf(" Relay pubkey: %s\n", relay_pubkey ? relay_pubkey : "NULL");
|
||||
@@ -2990,7 +3102,7 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
}
|
||||
|
||||
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
|
||||
if (!admin_pubkey) {
|
||||
DEBUG_ERROR("error: admin pubkey not available for authorization check");
|
||||
@@ -4400,6 +4512,179 @@ int handle_config_update_unified(cJSON* event, char* error_message, size_t error
|
||||
|
||||
|
||||
|
||||
// ================================
|
||||
// UNIFIED STARTUP FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Apply CLI overrides to existing config table in a single atomic operation
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options) {
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for CLI overrides");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!cli_options) {
|
||||
DEBUG_ERROR("CLI options not provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if there are any CLI overrides to apply
|
||||
int has_overrides = 0;
|
||||
if (cli_options->port_override > 0) has_overrides = 1;
|
||||
if (cli_options->admin_pubkey_override[0] != '\0') has_overrides = 1;
|
||||
if (cli_options->relay_privkey_override[0] != '\0') has_overrides = 1;
|
||||
|
||||
if (!has_overrides) {
|
||||
DEBUG_INFO("No CLI overrides to apply");
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Applying CLI overrides atomically");
|
||||
|
||||
// Begin transaction
|
||||
char* err_msg = NULL;
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to begin CLI overrides transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
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);
|
||||
|
||||
if (update_config_in_table("relay_port", port_str) != 0) {
|
||||
DEBUG_ERROR("Failed to update relay_port override");
|
||||
sqlite3_exec(g_db, "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') {
|
||||
if (update_config_in_table("admin_pubkey", cli_options->admin_pubkey_override) != 0) {
|
||||
DEBUG_ERROR("Failed to update admin_pubkey override");
|
||||
sqlite3_exec(g_db, "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') {
|
||||
if (update_config_in_table("relay_privkey", cli_options->relay_privkey_override) != 0) {
|
||||
DEBUG_ERROR("Failed to update relay_privkey override");
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO("Applied CLI override: relay_privkey");
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
rc = sqlite3_exec(g_db, "COMMIT", NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to commit CLI overrides transaction: %s", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Invalidate cache to force refresh with new values
|
||||
invalidate_config_cache();
|
||||
|
||||
DEBUG_INFO("Successfully applied CLI overrides atomically");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Populate all config values atomically in a single transaction
|
||||
int populate_all_config_values_atomic(const char* admin_pubkey, const char* relay_pubkey) {
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!admin_pubkey || !relay_pubkey) {
|
||||
DEBUG_ERROR("Admin pubkey or relay pubkey not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
char* err_msg = NULL;
|
||||
int rc = sqlite3_exec(g_db, "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_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_db));
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert all default config values
|
||||
for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; 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_db));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert admin_pubkey
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "admin_pubkey", -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 insert admin_pubkey: %s", sqlite3_errmsg(g_db));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert relay_pubkey
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_bind_text(stmt, 1, "relay_pubkey", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, relay_pubkey, -1, SQLITE_STATIC);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR("Failed to insert relay_pubkey: %s", sqlite3_errmsg(g_db));
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Commit transaction
|
||||
rc = sqlite3_exec(g_db, "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_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Successfully populated all config values atomically");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION CACHE MANAGEMENT
|
||||
// ================================
|
||||
@@ -4655,7 +4940,7 @@ int migrate_config_from_events_to_table(void) {
|
||||
DEBUG_INFO("Migrating configuration from events to config table...");
|
||||
|
||||
// Load the most recent configuration event from database
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
||||
if (!config_event) {
|
||||
DEBUG_INFO("No existing configuration event found - migration not needed");
|
||||
@@ -4803,7 +5088,7 @@ cJSON* generate_config_event_from_table(void) {
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||
// Try to get from unified cache
|
||||
relay_pubkey = get_relay_pubkey_cached();
|
||||
relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||
DEBUG_ERROR("Relay pubkey not available for config event generation");
|
||||
return NULL;
|
||||
|
||||
@@ -139,7 +139,7 @@ int get_config_bool(const char* key, int default_value);
|
||||
// First-time startup functions
|
||||
int is_first_time_startup(void);
|
||||
int first_time_startup_sequence(const cli_options_t* cli_options);
|
||||
int startup_existing_relay(const char* relay_pubkey);
|
||||
int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_options);
|
||||
|
||||
// Configuration application functions
|
||||
int apply_configuration_from_event(const cJSON* event);
|
||||
@@ -169,6 +169,7 @@ int set_config_value_in_table(const char* key, const char* value, const char* da
|
||||
const char* description, const char* category, int requires_restart);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
int populate_default_config_values(void);
|
||||
int populate_all_config_values_atomic(const char* admin_pubkey, const char* relay_pubkey);
|
||||
int add_pubkeys_to_config_table(void);
|
||||
|
||||
// Admin event processing functions (updated with WebSocket support)
|
||||
@@ -212,6 +213,9 @@ int populate_config_table_from_event(const cJSON* event);
|
||||
int process_startup_config_event(const cJSON* event);
|
||||
int process_startup_config_event_with_fallback(const cJSON* event);
|
||||
|
||||
// Atomic CLI override application
|
||||
int apply_cli_overrides_atomic(const cli_options_t* cli_options);
|
||||
|
||||
// Dynamic event generation functions for WebSocket configuration fetching
|
||||
cJSON* generate_config_event_from_table(void);
|
||||
int req_filter_requests_config_events(const cJSON* filter);
|
||||
|
||||
@@ -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);
|
||||
|
||||
// Forward declarations for config functions
|
||||
extern const char* get_relay_pubkey_cached(void);
|
||||
extern char* get_relay_private_key(void);
|
||||
extern const char* get_config_value(const char* key);
|
||||
extern int get_config_bool(const char* key, int default_value);
|
||||
extern const char* get_admin_pubkey_cached(void);
|
||||
|
||||
// Forward declarations for database functions
|
||||
extern int store_event(cJSON* event);
|
||||
@@ -1019,7 +1017,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
}
|
||||
|
||||
// Get relay keys for signing
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
char* relay_privkey_hex = get_relay_private_key();
|
||||
if (!relay_pubkey || !relay_privkey_hex) {
|
||||
if (relay_privkey_hex) free(relay_privkey_hex);
|
||||
@@ -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");
|
||||
|
||||
// Get relay pubkey for creating DM event
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
cJSON* success_dm = nostr_nip17_create_chat_event(
|
||||
response_content, // message content
|
||||
@@ -1518,7 +1516,7 @@ int is_nip17_gift_wrap_for_relay(cJSON* event) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey) {
|
||||
DEBUG_ERROR("NIP-17: Could not get relay pubkey for validation");
|
||||
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);
|
||||
|
||||
// Check if sender is admin
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0;
|
||||
|
||||
// Parse DM content as JSON array of commands
|
||||
|
||||
File diff suppressed because one or more lines are too long
112
src/main.c
112
src/main.c
@@ -318,6 +318,22 @@ int init_database(const char* database_path_override) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
// Check config table row count immediately after database open
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count immediately after sqlite3_open(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
DEBUG_LOG("Config table does not exist yet (first-time startup)");
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Check if database is already initialized by looking for the events table
|
||||
const char* check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='events'";
|
||||
sqlite3_stmt* check_stmt;
|
||||
@@ -1632,6 +1648,19 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
DEBUG_LOG("Database initialized for first-time startup");
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after init_database() (first-time): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Now that database is available, store the relay private key securely
|
||||
const char* relay_privkey = get_temp_relay_private_key();
|
||||
@@ -1649,6 +1678,9 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// COMMENTED OUT: Old incremental config building code replaced by unified startup sequence
|
||||
// The new first_time_startup_sequence() function handles all config creation atomically
|
||||
/*
|
||||
// Handle configuration setup after database is initialized
|
||||
// Always populate defaults directly in config table (abandoning legacy event signing)
|
||||
|
||||
@@ -1661,6 +1693,19 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after populate_default_config_values(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Apply CLI overrides now that database is available
|
||||
if (cli_options.port_override > 0) {
|
||||
char port_str[16];
|
||||
@@ -1683,6 +1728,20 @@ int main(int argc, char* argv[]) {
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after add_pubkeys_to_config_table() (first-time): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
*/
|
||||
} else {
|
||||
// Find existing database file
|
||||
char** existing_files = find_existing_db_files();
|
||||
@@ -1718,7 +1777,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// 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");
|
||||
cleanup_configuration_system();
|
||||
free(relay_pubkey);
|
||||
@@ -1761,25 +1820,29 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
DEBUG_LOG("Existing database initialized");
|
||||
|
||||
// Check config table row count after database initialization
|
||||
{
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// COMMENTED OUT: Old incremental config building code replaced by unified startup sequence
|
||||
// The new startup_existing_relay() function handles all config loading atomically
|
||||
/*
|
||||
// Ensure default configuration values are populated (for any missing keys)
|
||||
// This must be done AFTER database initialization
|
||||
// COMMENTED OUT: Don't modify existing database config on restart
|
||||
// if (populate_default_config_values() != 0) {
|
||||
// DEBUG_WARN("Failed to populate default config values for existing relay - continuing");
|
||||
// }
|
||||
|
||||
|
||||
// Load configuration from database
|
||||
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
||||
if (config_event) {
|
||||
@@ -1791,28 +1854,41 @@ int main(int argc, char* argv[]) {
|
||||
// This is expected for relays using table-based configuration
|
||||
// No longer a warning - just informational
|
||||
}
|
||||
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count before checking pubkeys: %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
|
||||
// Ensure pubkeys are in config table for existing relay
|
||||
// This handles migration from old event-based config to table-based config
|
||||
const char* admin_pubkey_from_table = get_config_value_from_table("admin_pubkey");
|
||||
const char* relay_pubkey_from_table = get_config_value_from_table("relay_pubkey");
|
||||
|
||||
|
||||
int need_to_add_pubkeys = 0;
|
||||
|
||||
|
||||
// Check if admin_pubkey is missing or invalid
|
||||
if (!admin_pubkey_from_table || strlen(admin_pubkey_from_table) != 64) {
|
||||
DEBUG_WARN("Admin pubkey missing or invalid in config table - will regenerate from cache");
|
||||
need_to_add_pubkeys = 1;
|
||||
}
|
||||
if (admin_pubkey_from_table) free((char*)admin_pubkey_from_table);
|
||||
|
||||
|
||||
// Check if relay_pubkey is missing or invalid
|
||||
if (!relay_pubkey_from_table || strlen(relay_pubkey_from_table) != 64) {
|
||||
DEBUG_WARN("Relay pubkey missing or invalid in config table - will regenerate from cache");
|
||||
need_to_add_pubkeys = 1;
|
||||
}
|
||||
if (relay_pubkey_from_table) free((char*)relay_pubkey_from_table);
|
||||
|
||||
|
||||
// If either pubkey is missing, call add_pubkeys_to_config_table to populate both
|
||||
if (need_to_add_pubkeys) {
|
||||
if (add_pubkeys_to_config_table() != 0) {
|
||||
@@ -1822,8 +1898,21 @@ int main(int argc, char* argv[]) {
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DEBUG_GUARD_START
|
||||
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int row_count = sqlite3_column_int(stmt, 0);
|
||||
DEBUG_LOG("Config table row count after add_pubkeys_to_config_table(): %d", row_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
// DEBUG_GUARD_END
|
||||
}
|
||||
|
||||
|
||||
// Apply CLI overrides for existing relay (port override should work even for existing relays)
|
||||
if (cli_options.port_override > 0) {
|
||||
char port_str[16];
|
||||
@@ -1837,6 +1926,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
||||
}
|
||||
*/
|
||||
|
||||
// Free memory
|
||||
free(relay_pubkey);
|
||||
|
||||
@@ -53,7 +53,6 @@ extern struct expiration_config {
|
||||
// Configuration functions from C-relay
|
||||
extern int get_config_bool(const char* key, int default_value);
|
||||
extern int get_config_int(const char* key, int default_value);
|
||||
extern const char* get_admin_pubkey_cached(void);
|
||||
|
||||
// NIP-42 constants (from nostr_core_lib)
|
||||
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242
|
||||
@@ -301,7 +300,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
// 8. Check if this is a kind 23456 admin event from authorized admin
|
||||
// This must happen AFTER signature validation but BEFORE auth rules
|
||||
if (event_kind == 23456) {
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||
// Valid admin event - bypass remaining validation
|
||||
cJSON_Delete(event);
|
||||
|
||||
@@ -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)) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
@@ -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
|
||||
if (event_kind == 23456 && event_pubkey) {
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||
bypass_auth = 1;
|
||||
} else {
|
||||
@@ -1159,7 +1159,7 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey) {
|
||||
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
|
||||
return -1;
|
||||
@@ -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);
|
||||
|
||||
// Check if sender is admin
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!admin_pubkey || strlen(admin_pubkey) == 0 ||
|
||||
strcmp(sender_pubkey, admin_pubkey) != 0) {
|
||||
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
|
||||
|
||||
Reference in New Issue
Block a user