Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b810982a17 |
66
README.md
66
README.md
@@ -11,10 +11,8 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
|||||||
- [x] NIP-01: Basic protocol flow implementation
|
- [x] NIP-01: Basic protocol flow implementation
|
||||||
- [x] NIP-09: Event deletion
|
- [x] NIP-09: Event deletion
|
||||||
- [x] NIP-11: Relay information document
|
- [x] NIP-11: Relay information document
|
||||||
- [ ] NIP-12: Generic tag queries
|
- [x] NIP-13: Proof of Work
|
||||||
- [ ] NIP-13: Proof of Work
|
|
||||||
- [x] NIP-15: End of Stored Events Notice
|
- [x] NIP-15: End of Stored Events Notice
|
||||||
- [ ] NIP-16: Event Treatment
|
|
||||||
- [x] NIP-20: Command Results
|
- [x] NIP-20: Command Results
|
||||||
- [ ] NIP-22: Event `created_at` Limits
|
- [ ] NIP-22: Event `created_at` Limits
|
||||||
- [ ] NIP-25: Reactions
|
- [ ] NIP-25: Reactions
|
||||||
@@ -27,3 +25,65 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
|||||||
- [ ] NIP-50: Keywords filter. [experimental](#search)
|
- [ ] NIP-50: Keywords filter. [experimental](#search)
|
||||||
- [ ] NIP-70: Protected Events
|
- [ ] NIP-70: Protected Events
|
||||||
|
|
||||||
|
## NIP-13: Proof of Work Configuration
|
||||||
|
|
||||||
|
The relay supports NIP-13 Proof of Work validation with configurable settings. PoW validation helps prevent spam and ensures computational commitment from event publishers.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Configure PoW validation using these environment variables:
|
||||||
|
|
||||||
|
- `RELAY_POW_ENABLED` - Enable/disable PoW validation (default: `1`)
|
||||||
|
- `1`, `true`, or `yes` to enable
|
||||||
|
- `0`, `false`, or `no` to disable
|
||||||
|
|
||||||
|
- `RELAY_MIN_POW_DIFFICULTY` - Minimum required difficulty (default: `0`)
|
||||||
|
- Range: `0-64` (reasonable bounds)
|
||||||
|
- `0` = no minimum requirement (events without PoW are accepted)
|
||||||
|
- Higher values require more computational work
|
||||||
|
|
||||||
|
- `RELAY_POW_MODE` - Validation mode (default: `basic`)
|
||||||
|
- `basic` - Basic PoW validation
|
||||||
|
- `full` - Full validation with nonce tag requirements
|
||||||
|
- `strict` - Strict anti-spam mode with committed target validation
|
||||||
|
- `disabled` - Disable PoW validation entirely
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic setup - accept events with or without PoW
|
||||||
|
export RELAY_POW_ENABLED=1
|
||||||
|
export RELAY_MIN_POW_DIFFICULTY=0
|
||||||
|
export RELAY_POW_MODE=basic
|
||||||
|
|
||||||
|
# Anti-spam setup - require minimum difficulty 16
|
||||||
|
export RELAY_POW_ENABLED=1
|
||||||
|
export RELAY_MIN_POW_DIFFICULTY=16
|
||||||
|
export RELAY_POW_MODE=strict
|
||||||
|
|
||||||
|
# Disable PoW validation completely
|
||||||
|
export RELAY_POW_ENABLED=0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- **min_difficulty=0**: Events without PoW are accepted; events with PoW are validated
|
||||||
|
- **min_difficulty>0**: All events must have valid PoW meeting minimum difficulty
|
||||||
|
- **strict mode**: Additional validation prevents difficulty commitment gaming
|
||||||
|
- **NIP-11 integration**: PoW configuration is advertised via relay information document
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Run the comprehensive PoW test suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tests/13_nip_test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The test suite validates:
|
||||||
|
- NIP-11 PoW support advertisement
|
||||||
|
- Event acceptance without PoW (when min_difficulty=0)
|
||||||
|
- Event validation with valid PoW
|
||||||
|
- Configuration via environment variables
|
||||||
|
- NIP-13 reference event validation
|
||||||
|
|
||||||
|
|||||||
BIN
c-relay-x86_64
Executable file
BIN
c-relay-x86_64
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule nostr_core_lib updated: 33129d82fd...55e2a9c68e
107
relay.log
107
relay.log
@@ -1,6 +1,8 @@
|
|||||||
[34m[1m=== C Nostr Relay Server ===[0m
|
[34m[1m=== C Nostr Relay Server ===[0m
|
||||||
[32m[SUCCESS][0m Database connection established
|
[32m[SUCCESS][0m Database connection established
|
||||||
[32m[SUCCESS][0m Relay information initialized with default values
|
[32m[SUCCESS][0m Relay information initialized with default values
|
||||||
|
[34m[INFO][0m Initializing NIP-13 Proof of Work configuration
|
||||||
|
[34m[INFO][0m PoW Configuration: enabled=true, min_difficulty=0, validation_flags=0x1, mode=full
|
||||||
[34m[INFO][0m Starting relay server...
|
[34m[INFO][0m Starting relay server...
|
||||||
[34m[INFO][0m Starting libwebsockets-based Nostr relay server...
|
[34m[INFO][0m Starting libwebsockets-based Nostr relay server...
|
||||||
[32m[SUCCESS][0m WebSocket relay started on ws://127.0.0.1:8888
|
[32m[SUCCESS][0m WebSocket relay started on ws://127.0.0.1:8888
|
||||||
@@ -9,4 +11,107 @@
|
|||||||
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
[34m[INFO][0m HTTP request received
|
[34m[INFO][0m HTTP request received
|
||||||
[34m[INFO][0m Handling NIP-11 relay information request
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
[33m[WARNING][0m HTTP request without proper Accept header for NIP-11
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=10, target=8, nonce=1839
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=21, target=20, nonce=776797
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=8, target=8, nonce=385
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=21, target=20, nonce=776797
|
||||||
|
[33m[WARNING][0m Event already exists in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=8, target=8, nonce=1669
|
||||||
|
[32m[SUCCESS][0m Event stored in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
[34m[INFO][0m HTTP request received
|
||||||
|
[34m[INFO][0m Handling NIP-11 relay information request
|
||||||
|
[32m[SUCCESS][0m NIP-11 relay information served successfully
|
||||||
|
[34m[INFO][0m WebSocket connection established
|
||||||
|
[34m[INFO][0m Received WebSocket message
|
||||||
|
[34m[INFO][0m Handling EVENT message with full NIP-01 validation
|
||||||
|
[34m[INFO][0m PoW validated: difficulty=21, target=20, nonce=776797
|
||||||
|
[33m[WARNING][0m Event already exists in database
|
||||||
|
[32m[SUCCESS][0m Event validated and stored successfully
|
||||||
|
[34m[INFO][0m WebSocket connection closed
|
||||||
|
|||||||
210
src/main.c
210
src/main.c
@@ -14,6 +14,7 @@
|
|||||||
// Include nostr_core_lib for Nostr functionality
|
// Include nostr_core_lib for Nostr functionality
|
||||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||||
|
#include "../nostr_core_lib/nostr_core/nip013.h" // NIP-13: Proof of Work
|
||||||
|
|
||||||
// Server Configuration
|
// Server Configuration
|
||||||
#define DEFAULT_PORT 8888
|
#define DEFAULT_PORT 8888
|
||||||
@@ -74,6 +75,28 @@ struct relay_info {
|
|||||||
// Global relay information instance
|
// Global relay information instance
|
||||||
static struct relay_info g_relay_info = {0};
|
static struct relay_info g_relay_info = {0};
|
||||||
|
|
||||||
|
// NIP-13 PoW configuration structure
|
||||||
|
struct pow_config {
|
||||||
|
int enabled; // 0 = disabled, 1 = enabled
|
||||||
|
int min_pow_difficulty; // Minimum required difficulty (0 = no requirement)
|
||||||
|
int validation_flags; // Bitflags for validation options
|
||||||
|
int require_nonce_tag; // 1 = require nonce tag presence
|
||||||
|
int reject_lower_targets; // 1 = reject if committed < actual difficulty
|
||||||
|
int strict_format; // 1 = enforce strict nonce tag format
|
||||||
|
int anti_spam_mode; // 1 = full anti-spam validation
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global PoW configuration instance
|
||||||
|
static struct pow_config g_pow_config = {
|
||||||
|
.enabled = 1, // Enable PoW validation by default
|
||||||
|
.min_pow_difficulty = 0, // No minimum difficulty by default
|
||||||
|
.validation_flags = NOSTR_POW_VALIDATE_BASIC,
|
||||||
|
.require_nonce_tag = 0, // Don't require nonce tags by default
|
||||||
|
.reject_lower_targets = 0, // Allow lower committed targets by default
|
||||||
|
.strict_format = 0, // Relaxed format validation by default
|
||||||
|
.anti_spam_mode = 0 // Basic validation by default
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -190,6 +213,10 @@ void cleanup_relay_info();
|
|||||||
cJSON* generate_relay_info_json();
|
cJSON* generate_relay_info_json();
|
||||||
int handle_nip11_http_request(struct lws* wsi, const char* accept_header);
|
int handle_nip11_http_request(struct lws* wsi, const char* accept_header);
|
||||||
|
|
||||||
|
// Forward declarations for NIP-13 PoW validation
|
||||||
|
void init_pow_config();
|
||||||
|
int validate_event_pow(cJSON* event, char* error_message, size_t error_size);
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -1238,6 +1265,7 @@ void init_relay_info() {
|
|||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
||||||
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
||||||
}
|
}
|
||||||
@@ -1251,7 +1279,7 @@ void init_relay_info() {
|
|||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", 100);
|
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", 100);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", 8196);
|
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", 8196);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", 0);
|
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
|
||||||
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", cJSON_False);
|
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", cJSON_False);
|
||||||
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
|
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
|
||||||
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
|
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
|
||||||
@@ -1564,6 +1592,171 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NIP-13 PROOF OF WORK VALIDATION
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Initialize PoW configuration with environment variables and defaults
|
||||||
|
void init_pow_config() {
|
||||||
|
log_info("Initializing NIP-13 Proof of Work configuration");
|
||||||
|
|
||||||
|
// Initialize with defaults (already set in struct initialization)
|
||||||
|
|
||||||
|
// Check environment variables for configuration
|
||||||
|
const char* pow_enabled_env = getenv("RELAY_POW_ENABLED");
|
||||||
|
if (pow_enabled_env) {
|
||||||
|
g_pow_config.enabled = (strcmp(pow_enabled_env, "1") == 0 ||
|
||||||
|
strcmp(pow_enabled_env, "true") == 0 ||
|
||||||
|
strcmp(pow_enabled_env, "yes") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* min_diff_env = getenv("RELAY_MIN_POW_DIFFICULTY");
|
||||||
|
if (min_diff_env) {
|
||||||
|
int min_diff = atoi(min_diff_env);
|
||||||
|
if (min_diff >= 0 && min_diff <= 64) { // Reasonable bounds
|
||||||
|
g_pow_config.min_pow_difficulty = min_diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* pow_mode_env = getenv("RELAY_POW_MODE");
|
||||||
|
if (pow_mode_env) {
|
||||||
|
if (strcmp(pow_mode_env, "strict") == 0) {
|
||||||
|
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||||
|
g_pow_config.require_nonce_tag = 1;
|
||||||
|
g_pow_config.reject_lower_targets = 1;
|
||||||
|
g_pow_config.strict_format = 1;
|
||||||
|
g_pow_config.anti_spam_mode = 1;
|
||||||
|
log_info("PoW configured in strict anti-spam mode");
|
||||||
|
} else if (strcmp(pow_mode_env, "full") == 0) {
|
||||||
|
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||||
|
g_pow_config.require_nonce_tag = 1;
|
||||||
|
log_info("PoW configured in full validation mode");
|
||||||
|
} else if (strcmp(pow_mode_env, "basic") == 0) {
|
||||||
|
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||||
|
log_info("PoW configured in basic validation mode");
|
||||||
|
} else if (strcmp(pow_mode_env, "disabled") == 0) {
|
||||||
|
g_pow_config.enabled = 0;
|
||||||
|
log_info("PoW validation disabled via RELAY_POW_MODE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log final configuration
|
||||||
|
char config_msg[512];
|
||||||
|
snprintf(config_msg, sizeof(config_msg),
|
||||||
|
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
||||||
|
g_pow_config.enabled ? "true" : "false",
|
||||||
|
g_pow_config.min_pow_difficulty,
|
||||||
|
g_pow_config.validation_flags,
|
||||||
|
g_pow_config.anti_spam_mode ? "anti-spam" :
|
||||||
|
(g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||||
|
log_info(config_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate event Proof of Work according to NIP-13
|
||||||
|
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||||
|
if (!g_pow_config.enabled) {
|
||||||
|
return 0; // PoW validation disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
snprintf(error_message, error_size, "pow: null event");
|
||||||
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If min_pow_difficulty is 0, only validate events that have nonce tags
|
||||||
|
// This allows events without PoW when difficulty requirement is 0
|
||||||
|
if (g_pow_config.min_pow_difficulty == 0) {
|
||||||
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
|
int has_nonce_tag = 0;
|
||||||
|
|
||||||
|
if (tags && cJSON_IsArray(tags)) {
|
||||||
|
cJSON* tag = NULL;
|
||||||
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
|
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
||||||
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
|
if (cJSON_IsString(tag_name)) {
|
||||||
|
const char* name = cJSON_GetStringValue(tag_name);
|
||||||
|
if (name && strcmp(name, "nonce") == 0) {
|
||||||
|
has_nonce_tag = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no minimum difficulty required and no nonce tag, skip PoW validation
|
||||||
|
if (!has_nonce_tag) {
|
||||||
|
return 0; // Accept event without PoW when min_difficulty=0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform PoW validation using nostr_core_lib
|
||||||
|
nostr_pow_result_t pow_result;
|
||||||
|
int validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
|
||||||
|
g_pow_config.validation_flags, &pow_result);
|
||||||
|
|
||||||
|
if (validation_result != NOSTR_SUCCESS) {
|
||||||
|
// Handle specific error cases with appropriate messages
|
||||||
|
switch (validation_result) {
|
||||||
|
case NOSTR_ERROR_NIP13_INSUFFICIENT:
|
||||||
|
snprintf(error_message, error_size,
|
||||||
|
"pow: insufficient difficulty: %d < %d",
|
||||||
|
pow_result.actual_difficulty, g_pow_config.min_pow_difficulty);
|
||||||
|
log_warning("Event rejected: insufficient PoW difficulty");
|
||||||
|
break;
|
||||||
|
case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
|
||||||
|
// This should not happen with min_difficulty=0 after our check above
|
||||||
|
if (g_pow_config.min_pow_difficulty > 0) {
|
||||||
|
snprintf(error_message, error_size, "pow: missing required nonce tag");
|
||||||
|
log_warning("Event rejected: missing nonce tag");
|
||||||
|
} else {
|
||||||
|
return 0; // Allow when min_difficulty=0
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG:
|
||||||
|
snprintf(error_message, error_size, "pow: invalid nonce tag format");
|
||||||
|
log_warning("Event rejected: invalid nonce tag format");
|
||||||
|
break;
|
||||||
|
case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
|
||||||
|
snprintf(error_message, error_size,
|
||||||
|
"pow: committed target (%d) lower than minimum (%d)",
|
||||||
|
pow_result.committed_target, g_pow_config.min_pow_difficulty);
|
||||||
|
log_warning("Event rejected: committed target too low (anti-spam protection)");
|
||||||
|
break;
|
||||||
|
case NOSTR_ERROR_NIP13_CALCULATION:
|
||||||
|
snprintf(error_message, error_size, "pow: difficulty calculation failed");
|
||||||
|
log_error("PoW difficulty calculation error");
|
||||||
|
break;
|
||||||
|
case NOSTR_ERROR_EVENT_INVALID_ID:
|
||||||
|
snprintf(error_message, error_size, "pow: invalid event ID format");
|
||||||
|
log_warning("Event rejected: invalid event ID for PoW calculation");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
snprintf(error_message, error_size, "pow: validation failed - %s",
|
||||||
|
strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error");
|
||||||
|
log_warning("Event rejected: PoW validation failed");
|
||||||
|
}
|
||||||
|
return validation_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log successful PoW validation (only if minimum difficulty is required)
|
||||||
|
if (g_pow_config.min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
|
||||||
|
char debug_msg[256];
|
||||||
|
snprintf(debug_msg, sizeof(debug_msg),
|
||||||
|
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
||||||
|
pow_result.actual_difficulty,
|
||||||
|
pow_result.committed_target,
|
||||||
|
(unsigned long long)pow_result.nonce_value,
|
||||||
|
pow_result.has_nonce_tag ? "" : " (no nonce tag)");
|
||||||
|
log_info(debug_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Success
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DATABASE FUNCTIONS
|
// DATABASE FUNCTIONS
|
||||||
@@ -2189,14 +2382,20 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
return signature_result;
|
return signature_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Complete event validation (combines structure + signature + additional checks)
|
// Step 3: Validate Proof of Work (NIP-13) if enabled
|
||||||
|
int pow_result = validate_event_pow(event, error_message, error_size);
|
||||||
|
if (pow_result != 0) {
|
||||||
|
return pow_result; // PoW validation failed, error message already set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Complete event validation (combines structure + signature + additional checks)
|
||||||
int validation_result = nostr_validate_event(event);
|
int validation_result = nostr_validate_event(event);
|
||||||
if (validation_result != NOSTR_SUCCESS) {
|
if (validation_result != NOSTR_SUCCESS) {
|
||||||
snprintf(error_message, error_size, "invalid: complete event validation failed");
|
snprintf(error_message, error_size, "invalid: complete event validation failed");
|
||||||
return validation_result;
|
return validation_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Check for special event types and handle accordingly
|
// Step 5: Check for special event types and handle accordingly
|
||||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||||
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||||
@@ -2236,7 +2435,7 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Store event in database
|
// Step 6: Store event in database
|
||||||
if (store_event(event) == 0) {
|
if (store_event(event) == 0) {
|
||||||
error_message[0] = '\0'; // Success - empty error message
|
error_message[0] = '\0'; // Success - empty error message
|
||||||
log_success("Event validated and stored successfully");
|
log_success("Event validated and stored successfully");
|
||||||
@@ -2603,6 +2802,9 @@ int main(int argc, char* argv[]) {
|
|||||||
// Initialize NIP-11 relay information
|
// Initialize NIP-11 relay information
|
||||||
init_relay_info();
|
init_relay_info();
|
||||||
|
|
||||||
|
// Initialize NIP-13 PoW configuration
|
||||||
|
init_pow_config();
|
||||||
|
|
||||||
log_info("Starting relay server...");
|
log_info("Starting relay server...");
|
||||||
|
|
||||||
// Start WebSocket Nostr relay server
|
// Start WebSocket Nostr relay server
|
||||||
|
|||||||
432
tests/11_nip_information.sh
Executable file
432
tests/11_nip_information.sh
Executable file
@@ -0,0 +1,432 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# NIP-11 Relay Information Document Test
|
||||||
|
# Tests HTTP endpoint for relay information according to NIP-11 specification
|
||||||
|
|
||||||
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# Color constants
|
||||||
|
RED='\033[31m'
|
||||||
|
GREEN='\033[32m'
|
||||||
|
YELLOW='\033[33m'
|
||||||
|
BLUE='\033[34m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
|
# Test configuration
|
||||||
|
RELAY_URL="http://127.0.0.1:8888"
|
||||||
|
RELAY_WS_URL="ws://127.0.0.1:8888"
|
||||||
|
|
||||||
|
# Print functions
|
||||||
|
print_header() {
|
||||||
|
echo -e "${BLUE}${BOLD}=== $1 ===${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_step() {
|
||||||
|
echo -e "${YELLOW}[STEP]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}✓${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}✗${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test functions
|
||||||
|
test_http_with_correct_header() {
|
||||||
|
print_step "Testing HTTP request with correct Accept header"
|
||||||
|
|
||||||
|
local response=""
|
||||||
|
local http_code=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
# Use curl to test with proper Accept header
|
||||||
|
response=$(curl -s -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "")
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "000")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == "200" ]]; then
|
||||||
|
print_success "HTTP 200 OK received with correct Accept header"
|
||||||
|
|
||||||
|
# Validate JSON response
|
||||||
|
if echo "$response" | jq . >/dev/null 2>&1; then
|
||||||
|
print_success "Response is valid JSON"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Response is not valid JSON"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "Expected HTTP 200, got HTTP $http_code"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_http_without_header() {
|
||||||
|
print_step "Testing HTTP request without Accept header (should return 406)"
|
||||||
|
|
||||||
|
local http_code=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$RELAY_URL/" 2>/dev/null || echo "000")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == "406" ]]; then
|
||||||
|
print_success "HTTP 406 Not Acceptable received without proper Accept header"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Expected HTTP 406, got HTTP $http_code"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_http_with_wrong_header() {
|
||||||
|
print_step "Testing HTTP request with wrong Accept header (should return 406)"
|
||||||
|
|
||||||
|
local http_code=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Accept: application/json" "$RELAY_URL/" 2>/dev/null || echo "000")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == "406" ]]; then
|
||||||
|
print_success "HTTP 406 Not Acceptable received with wrong Accept header"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Expected HTTP 406, got HTTP $http_code"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cors_headers() {
|
||||||
|
print_step "Testing CORS headers presence"
|
||||||
|
|
||||||
|
local headers=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
headers=$(curl -s -I -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cors_origin_found=false
|
||||||
|
local cors_headers_found=false
|
||||||
|
local cors_methods_found=false
|
||||||
|
|
||||||
|
if echo "$headers" | grep -qi "access-control-allow-origin"; then
|
||||||
|
cors_origin_found=true
|
||||||
|
print_success "Access-Control-Allow-Origin header found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$headers" | grep -qi "access-control-allow-headers"; then
|
||||||
|
cors_headers_found=true
|
||||||
|
print_success "Access-Control-Allow-Headers header found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$headers" | grep -qi "access-control-allow-methods"; then
|
||||||
|
cors_methods_found=true
|
||||||
|
print_success "Access-Control-Allow-Methods header found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$cors_origin_found" == true && "$cors_headers_found" == true && "$cors_methods_found" == true ]]; then
|
||||||
|
print_success "All required CORS headers present"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Missing CORS headers"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_json_structure() {
|
||||||
|
print_step "Testing NIP-11 JSON structure and required fields"
|
||||||
|
|
||||||
|
local response=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
response=$(curl -s -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$response" ]]; then
|
||||||
|
print_error "Empty response received"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate JSON structure using jq
|
||||||
|
if ! echo "$response" | jq . >/dev/null 2>&1; then
|
||||||
|
print_error "Response is not valid JSON"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Valid JSON structure confirmed"
|
||||||
|
|
||||||
|
# Check for required fields
|
||||||
|
local required_checks=0
|
||||||
|
local total_checks=0
|
||||||
|
|
||||||
|
# Test name field
|
||||||
|
((total_checks++))
|
||||||
|
if echo "$response" | jq -e '.name' >/dev/null 2>&1; then
|
||||||
|
local name=$(echo "$response" | jq -r '.name')
|
||||||
|
print_success "Name field present: $name"
|
||||||
|
((required_checks++))
|
||||||
|
else
|
||||||
|
print_warning "Name field missing (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test supported_nips field (required)
|
||||||
|
((total_checks++))
|
||||||
|
if echo "$response" | jq -e '.supported_nips' >/dev/null 2>&1; then
|
||||||
|
local nips=$(echo "$response" | jq -r '.supported_nips | @json')
|
||||||
|
print_success "Supported NIPs field present: $nips"
|
||||||
|
((required_checks++))
|
||||||
|
|
||||||
|
# Verify NIP-11 is in the supported list
|
||||||
|
if echo "$response" | jq -e '.supported_nips | contains([11])' >/dev/null 2>&1; then
|
||||||
|
print_success "NIP-11 correctly listed in supported NIPs"
|
||||||
|
else
|
||||||
|
print_warning "NIP-11 not found in supported NIPs list"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "Supported NIPs field missing (should be present)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test software field
|
||||||
|
((total_checks++))
|
||||||
|
if echo "$response" | jq -e '.software' >/dev/null 2>&1; then
|
||||||
|
local software=$(echo "$response" | jq -r '.software')
|
||||||
|
print_success "Software field present: $software"
|
||||||
|
((required_checks++))
|
||||||
|
else
|
||||||
|
print_warning "Software field missing (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test version field
|
||||||
|
((total_checks++))
|
||||||
|
if echo "$response" | jq -e '.version' >/dev/null 2>&1; then
|
||||||
|
local version=$(echo "$response" | jq -r '.version')
|
||||||
|
print_success "Version field present: $version"
|
||||||
|
((required_checks++))
|
||||||
|
else
|
||||||
|
print_warning "Version field missing (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test limitation object
|
||||||
|
((total_checks++))
|
||||||
|
if echo "$response" | jq -e '.limitation' >/dev/null 2>&1; then
|
||||||
|
print_success "Limitation object present"
|
||||||
|
((required_checks++))
|
||||||
|
|
||||||
|
# Check some common limitation fields
|
||||||
|
if echo "$response" | jq -e '.limitation.max_message_length' >/dev/null 2>&1; then
|
||||||
|
local max_msg=$(echo "$response" | jq -r '.limitation.max_message_length')
|
||||||
|
print_info " max_message_length: $max_msg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$response" | jq -e '.limitation.max_subscriptions' >/dev/null 2>&1; then
|
||||||
|
local max_subs=$(echo "$response" | jq -r '.limitation.max_subscriptions')
|
||||||
|
print_info " max_subscriptions: $max_subs"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "Limitation object missing (recommended)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test description field
|
||||||
|
if echo "$response" | jq -e '.description' >/dev/null 2>&1; then
|
||||||
|
local description=$(echo "$response" | jq -r '.description')
|
||||||
|
print_success "Description field present: ${description:0:50}..."
|
||||||
|
else
|
||||||
|
print_warning "Description field missing (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "JSON structure validation: $required_checks/$total_checks core fields present"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_content_type_header() {
|
||||||
|
print_step "Testing Content-Type header"
|
||||||
|
|
||||||
|
local headers=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
headers=$(curl -s -I -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$headers" | grep -qi "content-type.*application/nostr+json"; then
|
||||||
|
print_success "Correct Content-Type header: application/nostr+json"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_warning "Content-Type header not exactly 'application/nostr+json'"
|
||||||
|
echo "$headers" | grep -i "content-type" | head -1
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_non_root_path() {
|
||||||
|
print_step "Testing non-root path (should return 404)"
|
||||||
|
|
||||||
|
local http_code=""
|
||||||
|
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Accept: application/nostr+json" "$RELAY_URL/nonexistent" 2>/dev/null || echo "000")
|
||||||
|
else
|
||||||
|
print_error "curl command not found - required for NIP-11 testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == "404" ]]; then
|
||||||
|
print_success "HTTP 404 Not Found received for non-root path"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "Expected HTTP 404 for non-root path, got HTTP $http_code"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_websocket_still_works() {
|
||||||
|
print_step "Testing that WebSocket functionality still works on same port"
|
||||||
|
|
||||||
|
if ! command -v websocat &> /dev/null; then
|
||||||
|
print_warning "websocat not available - skipping WebSocket test"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to connect to WebSocket and send a simple REQ
|
||||||
|
local response=""
|
||||||
|
response=$(echo '["REQ","test_ws_nip11",{}]' | timeout 3s websocat "$RELAY_WS_URL" 2>/dev/null || echo "Connection failed")
|
||||||
|
|
||||||
|
if [[ "$response" == *"Connection failed"* ]]; then
|
||||||
|
print_error "WebSocket connection failed"
|
||||||
|
return 1
|
||||||
|
elif [[ "$response" == *"EOSE"* ]]; then
|
||||||
|
print_success "WebSocket still functional - received EOSE response"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_warning "WebSocket response unclear, but connection succeeded"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main test function
|
||||||
|
run_nip11_tests() {
|
||||||
|
print_header "NIP-11 Relay Information Document Tests"
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
print_step "Checking dependencies..."
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
print_error "curl command not found - required for NIP-11 HTTP testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
print_error "jq command not found - required for JSON validation"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
print_success "All dependencies found"
|
||||||
|
|
||||||
|
print_header "PHASE 1: Basic HTTP Functionality"
|
||||||
|
|
||||||
|
# Test 1: Correct Accept header
|
||||||
|
if ! test_http_with_correct_header; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 2: Missing Accept header
|
||||||
|
if ! test_http_without_header; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 3: Wrong Accept header
|
||||||
|
if ! test_http_with_wrong_header; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_header "PHASE 2: HTTP Headers Validation"
|
||||||
|
|
||||||
|
# Test 4: CORS headers
|
||||||
|
if ! test_cors_headers; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 5: Content-Type header
|
||||||
|
if ! test_content_type_header; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_header "PHASE 3: JSON Structure Validation"
|
||||||
|
|
||||||
|
# Test 6: JSON structure and required fields
|
||||||
|
if ! test_json_structure; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_header "PHASE 4: Additional Endpoint Behavior"
|
||||||
|
|
||||||
|
# Test 7: Non-root paths
|
||||||
|
if ! test_non_root_path; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 8: WebSocket compatibility
|
||||||
|
if ! test_websocket_still_works; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_header "PHASE 5: NIP-11 Compliance Summary"
|
||||||
|
|
||||||
|
# Final validation - get the actual response and display it
|
||||||
|
print_step "Displaying complete NIP-11 response..."
|
||||||
|
local response=""
|
||||||
|
if command -v curl &> /dev/null; then
|
||||||
|
response=$(curl -s -H "Accept: application/nostr+json" "$RELAY_URL/" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$response" ]] && echo "$response" | jq . >/dev/null 2>&1; then
|
||||||
|
echo "$response" | jq .
|
||||||
|
else
|
||||||
|
print_error "Failed to retrieve or parse final response"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "All NIP-11 tests passed!"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
print_header "Starting NIP-11 Relay Information Document Test Suite"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if run_nip11_tests; then
|
||||||
|
echo
|
||||||
|
print_success "All NIP-11 tests completed successfully!"
|
||||||
|
print_info "The C-Relay NIP-11 implementation is fully compliant"
|
||||||
|
print_info "✅ HTTP endpoint, Accept header validation, CORS, and JSON structure all working"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
print_error "Some NIP-11 tests failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
384
tests/13_nip_test.sh
Executable file
384
tests/13_nip_test.sh
Executable file
@@ -0,0 +1,384 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# NIP-13 Proof of Work Validation Test Suite for C Nostr Relay
|
||||||
|
# Tests PoW validation in the relay's event processing pipeline
|
||||||
|
# Based on nostr_core_lib/tests/nip13_test.c
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Color constants
|
||||||
|
RED='\033[31m'
|
||||||
|
GREEN='\033[32m'
|
||||||
|
YELLOW='\033[33m'
|
||||||
|
BLUE='\033[34m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
|
# Test configuration
|
||||||
|
RELAY_URL="ws://127.0.0.1:8888"
|
||||||
|
HTTP_URL="http://127.0.0.1:8888"
|
||||||
|
TEST_COUNT=0
|
||||||
|
PASSED_COUNT=0
|
||||||
|
FAILED_COUNT=0
|
||||||
|
|
||||||
|
# Test results tracking
|
||||||
|
declare -a TEST_RESULTS=()
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_test_header() {
|
||||||
|
TEST_COUNT=$((TEST_COUNT + 1))
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}=== TEST $TEST_COUNT: $1 ===${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
record_test_result() {
|
||||||
|
local test_name="$1"
|
||||||
|
local result="$2"
|
||||||
|
local details="$3"
|
||||||
|
|
||||||
|
TEST_RESULTS+=("$test_name|$result|$details")
|
||||||
|
|
||||||
|
if [ "$result" = "PASS" ]; then
|
||||||
|
PASSED_COUNT=$((PASSED_COUNT + 1))
|
||||||
|
print_success "PASS: $test_name"
|
||||||
|
else
|
||||||
|
FAILED_COUNT=$((FAILED_COUNT + 1))
|
||||||
|
print_error "FAIL: $test_name"
|
||||||
|
if [ -n "$details" ]; then
|
||||||
|
echo " Details: $details"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if relay is running
|
||||||
|
check_relay_running() {
|
||||||
|
print_info "Checking if relay is running..."
|
||||||
|
|
||||||
|
if ! curl -s -H "Accept: application/nostr+json" "$HTTP_URL/" >/dev/null 2>&1; then
|
||||||
|
print_error "Relay is not running or not accessible at $HTTP_URL"
|
||||||
|
print_info "Please start the relay with: ./make_and_restart_relay.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Relay is running and accessible"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test NIP-11 relay information includes NIP-13
|
||||||
|
test_nip11_pow_support() {
|
||||||
|
print_test_header "NIP-11 PoW Support Advertisement"
|
||||||
|
|
||||||
|
print_info "Fetching relay information..."
|
||||||
|
RELAY_INFO=$(curl -s -H "Accept: application/nostr+json" "$HTTP_URL/")
|
||||||
|
|
||||||
|
echo "Relay Info Response:"
|
||||||
|
echo "$RELAY_INFO" | jq '.'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if NIP-13 is in supported_nips
|
||||||
|
if echo "$RELAY_INFO" | jq -e '.supported_nips | index(13)' >/dev/null 2>&1; then
|
||||||
|
print_success "✓ NIP-13 found in supported_nips array"
|
||||||
|
NIP13_SUPPORTED=true
|
||||||
|
else
|
||||||
|
print_error "✗ NIP-13 not found in supported_nips array"
|
||||||
|
NIP13_SUPPORTED=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if min_pow_difficulty is present
|
||||||
|
MIN_POW_DIFF=$(echo "$RELAY_INFO" | jq -r '.limitation.min_pow_difficulty // "missing"')
|
||||||
|
if [ "$MIN_POW_DIFF" != "missing" ]; then
|
||||||
|
print_success "✓ min_pow_difficulty found: $MIN_POW_DIFF"
|
||||||
|
MIN_POW_PRESENT=true
|
||||||
|
else
|
||||||
|
print_error "✗ min_pow_difficulty not found in limitations"
|
||||||
|
MIN_POW_PRESENT=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NIP13_SUPPORTED" = true ] && [ "$MIN_POW_PRESENT" = true ]; then
|
||||||
|
record_test_result "NIP-11 PoW Support Advertisement" "PASS" "NIP-13 supported, min_pow_difficulty=$MIN_POW_DIFF"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
record_test_result "NIP-11 PoW Support Advertisement" "FAIL" "Missing NIP-13 support or min_pow_difficulty"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test event submission without PoW (should be accepted when min_difficulty=0)
|
||||||
|
test_event_without_pow() {
|
||||||
|
print_test_header "Event Submission Without PoW (min_difficulty=0)"
|
||||||
|
|
||||||
|
# Create a simple event without PoW
|
||||||
|
print_info "Generating test event without PoW..."
|
||||||
|
|
||||||
|
# Use nak to generate a simple event
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
print_warning "nak command not found - skipping PoW generation tests"
|
||||||
|
record_test_result "Event Submission Without PoW" "SKIP" "nak not available"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate event without PoW using direct private key
|
||||||
|
PRIVATE_KEY="91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"
|
||||||
|
EVENT_JSON=$(nak event --sec "$PRIVATE_KEY" -c "Test event without PoW" --ts $(date +%s))
|
||||||
|
|
||||||
|
print_info "Generated event:"
|
||||||
|
echo "$EVENT_JSON" | jq '.'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Send event to relay via WebSocket using websocat
|
||||||
|
print_info "Sending event to relay..."
|
||||||
|
|
||||||
|
# Create EVENT message in Nostr format
|
||||||
|
EVENT_MESSAGE="[\"EVENT\",$EVENT_JSON]"
|
||||||
|
|
||||||
|
# Send to relay and capture response
|
||||||
|
if command -v websocat &> /dev/null; then
|
||||||
|
RESPONSE=$(echo "$EVENT_MESSAGE" | timeout 5s websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||||
|
|
||||||
|
print_info "Relay response: $RESPONSE"
|
||||||
|
|
||||||
|
if [[ "$RESPONSE" == *"Connection failed"* ]]; then
|
||||||
|
print_error "✗ Failed to connect to relay"
|
||||||
|
record_test_result "Event Submission Without PoW" "FAIL" "Connection failed"
|
||||||
|
return 1
|
||||||
|
elif [[ "$RESPONSE" == *"true"* ]]; then
|
||||||
|
print_success "✓ Event without PoW accepted (expected when min_difficulty=0)"
|
||||||
|
record_test_result "Event Submission Without PoW" "PASS" "Event accepted as expected"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "✗ Event without PoW rejected (unexpected when min_difficulty=0)"
|
||||||
|
record_test_result "Event Submission Without PoW" "FAIL" "Event rejected: $RESPONSE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "websocat not found - required for testing"
|
||||||
|
record_test_result "Event Submission Without PoW" "SKIP" "websocat not available"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test event with valid PoW
|
||||||
|
test_event_with_pow() {
|
||||||
|
print_test_header "Event Submission With Valid PoW"
|
||||||
|
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
print_warning "nak command not found - skipping PoW validation tests"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "SKIP" "nak not available"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Generating event with PoW difficulty 8..."
|
||||||
|
|
||||||
|
# Generate event with PoW (difficulty 8 for reasonable test time) using direct private key
|
||||||
|
PRIVATE_KEY="91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"
|
||||||
|
POW_EVENT_JSON=$(nak event --sec "$PRIVATE_KEY" -c "Test event with PoW difficulty 8" --pow 8 --ts $(date +%s))
|
||||||
|
|
||||||
|
if [ -z "$POW_EVENT_JSON" ]; then
|
||||||
|
print_error "Failed to generate PoW event"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "FAIL" "PoW event generation failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Generated PoW event:"
|
||||||
|
echo "$POW_EVENT_JSON" | jq '.'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Extract nonce info for verification
|
||||||
|
NONCE_TAG=$(echo "$POW_EVENT_JSON" | jq -r '.tags[] | select(.[0] == "nonce") | .[1]' 2>/dev/null || echo "")
|
||||||
|
TARGET_DIFF=$(echo "$POW_EVENT_JSON" | jq -r '.tags[] | select(.[0] == "nonce") | .[2]' 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -n "$NONCE_TAG" ] && [ -n "$TARGET_DIFF" ]; then
|
||||||
|
print_info "PoW details: nonce=$NONCE_TAG, target_difficulty=$TARGET_DIFF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Send event to relay via WebSocket using websocat
|
||||||
|
print_info "Sending PoW event to relay..."
|
||||||
|
|
||||||
|
# Create EVENT message in Nostr format
|
||||||
|
POW_EVENT_MESSAGE="[\"EVENT\",$POW_EVENT_JSON]"
|
||||||
|
|
||||||
|
# Send to relay and capture response
|
||||||
|
if command -v websocat &> /dev/null; then
|
||||||
|
RESPONSE=$(echo "$POW_EVENT_MESSAGE" | timeout 10s websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||||
|
|
||||||
|
print_info "Relay response: $RESPONSE"
|
||||||
|
|
||||||
|
if [[ "$RESPONSE" == *"Connection failed"* ]]; then
|
||||||
|
print_error "✗ Failed to connect to relay"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "FAIL" "Connection failed"
|
||||||
|
return 1
|
||||||
|
elif [[ "$RESPONSE" == *"true"* ]]; then
|
||||||
|
print_success "✓ Event with valid PoW accepted"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "PASS" "PoW event accepted"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "✗ Event with valid PoW rejected"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "FAIL" "PoW event rejected: $RESPONSE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "websocat not found - required for testing"
|
||||||
|
record_test_result "Event Submission With Valid PoW" "SKIP" "websocat not available"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test relay configuration with environment variables
|
||||||
|
test_pow_configuration() {
|
||||||
|
print_test_header "PoW Configuration Via Environment Variables"
|
||||||
|
|
||||||
|
print_info "Testing different PoW configurations requires relay restart"
|
||||||
|
print_info "Current configuration from logs:"
|
||||||
|
|
||||||
|
if [ -f "relay.log" ]; then
|
||||||
|
grep "PoW Configuration:" relay.log | tail -1
|
||||||
|
else
|
||||||
|
print_warning "No relay.log found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test current configuration values
|
||||||
|
RELAY_INFO=$(curl -s -H "Accept: application/nostr+json" "$HTTP_URL/")
|
||||||
|
MIN_POW_DIFF=$(echo "$RELAY_INFO" | jq -r '.limitation.min_pow_difficulty')
|
||||||
|
|
||||||
|
print_info "Current min_pow_difficulty from NIP-11: $MIN_POW_DIFF"
|
||||||
|
|
||||||
|
# For now, just verify the configuration is readable
|
||||||
|
if [ "$MIN_POW_DIFF" != "null" ] && [ "$MIN_POW_DIFF" != "missing" ]; then
|
||||||
|
print_success "✓ PoW configuration is accessible via NIP-11"
|
||||||
|
record_test_result "PoW Configuration Via Environment Variables" "PASS" "min_pow_difficulty=$MIN_POW_DIFF"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "✗ PoW configuration not accessible"
|
||||||
|
record_test_result "PoW Configuration Via Environment Variables" "FAIL" "Cannot read min_pow_difficulty"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test NIP-13 reference event validation
|
||||||
|
test_nip13_reference_event() {
|
||||||
|
print_test_header "NIP-13 Reference Event Validation"
|
||||||
|
|
||||||
|
# This is the official NIP-13 reference event
|
||||||
|
NIP13_REF_EVENT='{"id":"000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358","pubkey":"a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243","created_at":1651794653,"kind":1,"tags":[["nonce","776797","20"]],"content":"It'\''s just me mining my own business","sig":"284622fc0a3f4f1303455d5175f7ba962a3300d136085b9566801bc2e0699de0c7e31e44c81fb40ad9049173742e904713c3594a1da0fc5d2382a25c11aba977"}'
|
||||||
|
|
||||||
|
print_info "Testing NIP-13 reference event from specification:"
|
||||||
|
echo "$NIP13_REF_EVENT" | jq '.'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Send reference event to relay via WebSocket using websocat
|
||||||
|
print_info "Sending NIP-13 reference event to relay..."
|
||||||
|
|
||||||
|
# Create EVENT message in Nostr format
|
||||||
|
REF_EVENT_MESSAGE="[\"EVENT\",$NIP13_REF_EVENT]"
|
||||||
|
|
||||||
|
# Send to relay and capture response
|
||||||
|
if command -v websocat &> /dev/null; then
|
||||||
|
RESPONSE=$(echo "$REF_EVENT_MESSAGE" | timeout 10s websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||||
|
|
||||||
|
print_info "Relay response: $RESPONSE"
|
||||||
|
|
||||||
|
if [[ "$RESPONSE" == *"Connection failed"* ]] || [[ -z "$RESPONSE" ]]; then
|
||||||
|
print_error "✗ Failed to connect to relay or no response"
|
||||||
|
record_test_result "NIP-13 Reference Event Validation" "FAIL" "Connection failed or timeout"
|
||||||
|
return 1
|
||||||
|
elif [[ "$RESPONSE" == *"true"* ]]; then
|
||||||
|
print_success "✓ NIP-13 reference event accepted"
|
||||||
|
record_test_result "NIP-13 Reference Event Validation" "PASS" "Reference event accepted"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "✗ NIP-13 reference event rejected"
|
||||||
|
record_test_result "NIP-13 Reference Event Validation" "FAIL" "Reference event rejected: $RESPONSE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "websocat not found - required for testing"
|
||||||
|
record_test_result "NIP-13 Reference Event Validation" "SKIP" "websocat not available"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print test summary
|
||||||
|
print_test_summary() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}=== TEST SUMMARY ===${RESET}"
|
||||||
|
echo "Total tests run: $TEST_COUNT"
|
||||||
|
echo -e "${GREEN}Passed: $PASSED_COUNT${RESET}"
|
||||||
|
echo -e "${RED}Failed: $FAILED_COUNT${RESET}"
|
||||||
|
|
||||||
|
if [ $FAILED_COUNT -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}${BOLD}Failed tests:${RESET}"
|
||||||
|
for result in "${TEST_RESULTS[@]}"; do
|
||||||
|
IFS='|' read -r name status details <<< "$result"
|
||||||
|
if [ "$status" = "FAIL" ]; then
|
||||||
|
echo -e " ${RED}✗ $name${RESET}"
|
||||||
|
if [ -n "$details" ]; then
|
||||||
|
echo " $details"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ $FAILED_COUNT -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}${BOLD}🎉 ALL TESTS PASSED!${RESET}"
|
||||||
|
echo -e "${GREEN}✅ NIP-13 PoW validation is working correctly in the relay${RESET}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}${BOLD}❌ SOME TESTS FAILED${RESET}"
|
||||||
|
echo "Please review the output above and check relay logs for more details."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main test execution
|
||||||
|
main() {
|
||||||
|
echo -e "${BOLD}=== NIP-13 Proof of Work Relay Test Suite ===${RESET}"
|
||||||
|
echo "Testing NIP-13 PoW validation in the C Nostr Relay"
|
||||||
|
echo "Relay URL: $RELAY_URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
print_error "curl is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
print_error "jq is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v websocat &> /dev/null; then
|
||||||
|
print_warning "websocat not found - WebSocket tests will be skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
check_relay_running
|
||||||
|
test_nip11_pow_support
|
||||||
|
test_event_without_pow
|
||||||
|
test_event_with_pow
|
||||||
|
test_pow_configuration
|
||||||
|
test_nip13_reference_event
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print_test_summary
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user