Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b09e7ac9 | ||
|
|
c76f10491a |
70
deploy_lt.sh
Executable file
70
deploy_lt.sh
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deployment script for Superball Thrower to lt server
|
||||||
|
# This script builds the binary locally and deploys it to the server
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Superball Thrower Deployment Script ==="
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SERVER="ubuntu@laantungir.com"
|
||||||
|
DEPLOY_DIR="/usr/local/bin/super_ball_thrower"
|
||||||
|
BINARY_NAME="superball_thrower"
|
||||||
|
SERVICE_NAME="superball-thrower"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Step 1: Clean previous build
|
||||||
|
echo -e "${YELLOW}[1/6] Cleaning previous build...${NC}"
|
||||||
|
make clean || true
|
||||||
|
|
||||||
|
# Step 2: Build the project
|
||||||
|
echo -e "${YELLOW}[2/6] Building superball_thrower...${NC}"
|
||||||
|
make
|
||||||
|
|
||||||
|
# Check if build was successful
|
||||||
|
if [ ! -f "$BINARY_NAME" ]; then
|
||||||
|
echo -e "${RED}Error: Build failed - binary not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Build successful!${NC}"
|
||||||
|
|
||||||
|
# Step 3: Stop the service on the server
|
||||||
|
echo -e "${YELLOW}[3/6] Stopping service on server...${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl stop $SERVICE_NAME" || echo "Service not running or doesn't exist yet"
|
||||||
|
|
||||||
|
# Step 4: Deploy binary to server
|
||||||
|
echo -e "${YELLOW}[4/6] Deploying binary to server...${NC}"
|
||||||
|
scp $BINARY_NAME $SERVER:~/$BINARY_NAME
|
||||||
|
|
||||||
|
# Step 5: Move binary to final location with proper permissions
|
||||||
|
echo -e "${YELLOW}[5/6] Installing binary...${NC}"
|
||||||
|
ssh $SERVER "sudo mv ~/$BINARY_NAME $DEPLOY_DIR/$BINARY_NAME && \
|
||||||
|
sudo chown superball-thrower:superball-thrower $DEPLOY_DIR/$BINARY_NAME && \
|
||||||
|
sudo chmod 755 $DEPLOY_DIR/$BINARY_NAME"
|
||||||
|
|
||||||
|
# Step 6: Restart the service
|
||||||
|
echo -e "${YELLOW}[6/6] Starting service...${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl start $SERVICE_NAME"
|
||||||
|
|
||||||
|
# Wait a moment for service to start
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Service Status:${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl status $SERVICE_NAME --no-pager" || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}=== Deployment Complete ===${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " View logs: ssh $SERVER 'sudo journalctl -u $SERVICE_NAME -f'"
|
||||||
|
echo " Check status: ssh $SERVER 'sudo systemctl status $SERVICE_NAME'"
|
||||||
|
echo " Restart: ssh $SERVER 'sudo systemctl restart $SERVICE_NAME'"
|
||||||
|
echo " Stop: ssh $SERVER 'sudo systemctl stop $SERVICE_NAME'"
|
||||||
186
main.c
186
main.c
@@ -29,7 +29,7 @@
|
|||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
#define THROWER_VERSION "v0.0.1"
|
#define THROWER_VERSION "v0.0.2"
|
||||||
|
|
||||||
// Configuration constants
|
// Configuration constants
|
||||||
#define MAX_RELAYS 50
|
#define MAX_RELAYS 50
|
||||||
@@ -177,6 +177,8 @@ static void free_padding_payload(padding_payload_t* payload);
|
|||||||
|
|
||||||
// Thrower info functions
|
// Thrower info functions
|
||||||
static int publish_thrower_info(superball_thrower_t* thrower);
|
static int publish_thrower_info(superball_thrower_t* thrower);
|
||||||
|
static int publish_metadata(superball_thrower_t* thrower);
|
||||||
|
static int publish_relay_list(superball_thrower_t* thrower);
|
||||||
static void* auto_publish_thread_func(void* arg);
|
static void* auto_publish_thread_func(void* arg);
|
||||||
|
|
||||||
// Main functions
|
// Main functions
|
||||||
@@ -793,6 +795,13 @@ static void forward_to_next_thrower(superball_thrower_t* thrower, cJSON* event,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the event JSON being published
|
||||||
|
char* event_json = cJSON_Print(signed_event);
|
||||||
|
if (event_json) {
|
||||||
|
log_message(LOG_INFO, "Publishing routing event JSON:\n%s", event_json);
|
||||||
|
free(event_json);
|
||||||
|
}
|
||||||
|
|
||||||
// Publish to relays
|
// Publish to relays
|
||||||
nostr_relay_pool_publish_async(thrower->pool, (const char**)routing->relays,
|
nostr_relay_pool_publish_async(thrower->pool, (const char**)routing->relays,
|
||||||
routing->relay_count, signed_event,
|
routing->relay_count, signed_event,
|
||||||
@@ -803,14 +812,29 @@ static void forward_to_next_thrower(superball_thrower_t* thrower, cJSON* event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void post_final_event(superball_thrower_t* thrower, cJSON* event, routing_payload_t* routing) {
|
static void post_final_event(superball_thrower_t* thrower, cJSON* event, routing_payload_t* routing) {
|
||||||
|
(void)event; // The wrapped event is not used - we publish the inner event from routing
|
||||||
|
|
||||||
log_message(LOG_INFO, "Posting final event to %d relays", routing->relay_count);
|
log_message(LOG_INFO, "Posting final event to %d relays", routing->relay_count);
|
||||||
|
|
||||||
// Publish the inner event directly
|
// The inner event is in routing->event (this is the kind 1 note, not the kind 22222 wrapper)
|
||||||
|
if (!routing->event) {
|
||||||
|
log_message(LOG_ERROR, "No inner event to publish");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the inner event JSON being published
|
||||||
|
char* event_json = cJSON_Print(routing->event);
|
||||||
|
if (event_json) {
|
||||||
|
log_message(LOG_INFO, "Publishing final event JSON:\n%s", event_json);
|
||||||
|
free(event_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish the inner event directly (this is the actual kind 1 note)
|
||||||
nostr_relay_pool_publish_async(thrower->pool, (const char**)routing->relays,
|
nostr_relay_pool_publish_async(thrower->pool, (const char**)routing->relays,
|
||||||
routing->relay_count, event,
|
routing->relay_count, routing->event,
|
||||||
publish_callback, thrower);
|
publish_callback, thrower);
|
||||||
|
|
||||||
cJSON* id = cJSON_GetObjectItem(event, "id");
|
cJSON* id = cJSON_GetObjectItem(routing->event, "id");
|
||||||
if (id) {
|
if (id) {
|
||||||
log_message(LOG_INFO, "Final event posted: %.16s...", cJSON_GetStringValue(id));
|
log_message(LOG_INFO, "Final event posted: %.16s...", cJSON_GetStringValue(id));
|
||||||
}
|
}
|
||||||
@@ -818,12 +842,23 @@ static void post_final_event(superball_thrower_t* thrower, cJSON* event, routing
|
|||||||
|
|
||||||
static void publish_callback(const char* relay_url, const char* event_id, int success,
|
static void publish_callback(const char* relay_url, const char* event_id, int success,
|
||||||
const char* message, void* user_data) {
|
const char* message, void* user_data) {
|
||||||
(void)user_data; // Suppress unused parameter warning
|
superball_thrower_t* thrower = (superball_thrower_t*)user_data;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
log_message(LOG_INFO, "✅ Published to %s: %.16s...", relay_url, event_id);
|
log_message(LOG_INFO, "✅ Published to %s: %.16s...", relay_url, event_id);
|
||||||
|
|
||||||
|
// Print relay response if available
|
||||||
|
if (message) {
|
||||||
|
log_message(LOG_DEBUG, "Relay response: %s", message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log_message(LOG_ERROR, "❌ Failed to publish to %s: %s", relay_url, message ? message : "unknown error");
|
log_message(LOG_ERROR, "❌ Failed to publish to %s: %s", relay_url, message ? message : "unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: We don't have access to the full event JSON here in the callback
|
||||||
|
// The event was already published. To see the full event, we'd need to
|
||||||
|
// log it before calling nostr_relay_pool_publish_async
|
||||||
|
(void)thrower; // Suppress unused warning for now
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free_routing_payload(routing_payload_t* payload) {
|
static void free_routing_payload(routing_payload_t* payload) {
|
||||||
@@ -940,6 +975,143 @@ static int publish_thrower_info(superball_thrower_t* thrower) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int publish_metadata(superball_thrower_t* thrower) {
|
||||||
|
log_message(LOG_INFO, "Publishing metadata (kind 0)...");
|
||||||
|
|
||||||
|
// Create metadata JSON content
|
||||||
|
cJSON* metadata = cJSON_CreateObject();
|
||||||
|
|
||||||
|
if (thrower->config->name) {
|
||||||
|
cJSON_AddStringToObject(metadata, "name", thrower->config->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thrower->config->description) {
|
||||||
|
cJSON_AddStringToObject(metadata, "about", thrower->config->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Superball-specific fields
|
||||||
|
if (thrower->config->software) {
|
||||||
|
cJSON_AddStringToObject(metadata, "nip05", thrower->config->software);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add version and supported SUPs
|
||||||
|
if (thrower->config->version) {
|
||||||
|
cJSON_AddStringToObject(metadata, "display_name",
|
||||||
|
thrower->config->version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thrower->config->supported_sups) {
|
||||||
|
cJSON_AddStringToObject(metadata, "website",
|
||||||
|
thrower->config->supported_sups);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* content = cJSON_PrintUnformatted(metadata);
|
||||||
|
cJSON_Delete(metadata);
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
log_message(LOG_ERROR, "Failed to create metadata JSON");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create kind 0 event with empty tags
|
||||||
|
cJSON* tags = cJSON_CreateArray();
|
||||||
|
cJSON* event = nostr_create_and_sign_event(0, content, tags,
|
||||||
|
thrower->private_key, time(NULL));
|
||||||
|
free(content);
|
||||||
|
cJSON_Delete(tags);
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
log_message(LOG_ERROR, "Failed to create metadata event");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get write-capable relays
|
||||||
|
const char** relay_urls = malloc(thrower->config->relay_count * sizeof(char*));
|
||||||
|
int relay_count = 0;
|
||||||
|
for (int i = 0; i < thrower->config->relay_count; i++) {
|
||||||
|
if (thrower->config->relays[i].write &&
|
||||||
|
strcmp(thrower->config->relays[i].auth_status, "no-auth") == 0) {
|
||||||
|
relay_urls[relay_count++] = thrower->config->relays[i].url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relay_count == 0) {
|
||||||
|
log_message(LOG_WARN, "No write-capable relays for metadata");
|
||||||
|
free(relay_urls);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nostr_relay_pool_publish_async(thrower->pool, relay_urls, relay_count,
|
||||||
|
event, publish_callback, thrower);
|
||||||
|
|
||||||
|
free(relay_urls);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
|
||||||
|
log_message(LOG_INFO, "Metadata published to %d relays", relay_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int publish_relay_list(superball_thrower_t* thrower) {
|
||||||
|
log_message(LOG_INFO, "Publishing relay list (kind 10002)...");
|
||||||
|
|
||||||
|
// Create tags array with relay information
|
||||||
|
cJSON* tags = cJSON_CreateArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < thrower->config->relay_count; i++) {
|
||||||
|
cJSON* relay_tag = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(relay_tag, cJSON_CreateString("r"));
|
||||||
|
cJSON_AddItemToArray(relay_tag, cJSON_CreateString(thrower->config->relays[i].url));
|
||||||
|
|
||||||
|
// Add read/write markers
|
||||||
|
if (thrower->config->relays[i].read && thrower->config->relays[i].write) {
|
||||||
|
// Both read and write - no marker needed (default)
|
||||||
|
} else if (thrower->config->relays[i].read) {
|
||||||
|
cJSON_AddItemToArray(relay_tag, cJSON_CreateString("read"));
|
||||||
|
} else if (thrower->config->relays[i].write) {
|
||||||
|
cJSON_AddItemToArray(relay_tag, cJSON_CreateString("write"));
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_AddItemToArray(tags, relay_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create kind 10002 event with empty content
|
||||||
|
cJSON* event = nostr_create_and_sign_event(10002, "", tags,
|
||||||
|
thrower->private_key, time(NULL));
|
||||||
|
cJSON_Delete(tags);
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
log_message(LOG_ERROR, "Failed to create relay list event");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get write-capable relays
|
||||||
|
const char** relay_urls = malloc(thrower->config->relay_count * sizeof(char*));
|
||||||
|
int relay_count = 0;
|
||||||
|
for (int i = 0; i < thrower->config->relay_count; i++) {
|
||||||
|
if (thrower->config->relays[i].write &&
|
||||||
|
strcmp(thrower->config->relays[i].auth_status, "no-auth") == 0) {
|
||||||
|
relay_urls[relay_count++] = thrower->config->relays[i].url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relay_count == 0) {
|
||||||
|
log_message(LOG_WARN, "No write-capable relays for relay list");
|
||||||
|
free(relay_urls);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nostr_relay_pool_publish_async(thrower->pool, relay_urls, relay_count,
|
||||||
|
event, publish_callback, thrower);
|
||||||
|
|
||||||
|
free(relay_urls);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
|
||||||
|
log_message(LOG_INFO, "Relay list published to %d relays", relay_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void* auto_publish_thread_func(void* arg) {
|
static void* auto_publish_thread_func(void* arg) {
|
||||||
superball_thrower_t* thrower = (superball_thrower_t*)arg;
|
superball_thrower_t* thrower = (superball_thrower_t*)arg;
|
||||||
|
|
||||||
@@ -1078,6 +1250,10 @@ static int thrower_start(superball_thrower_t* thrower) {
|
|||||||
|
|
||||||
log_message(LOG_INFO, "Monitoring %d relays for routing events", thrower->config->relay_count);
|
log_message(LOG_INFO, "Monitoring %d relays for routing events", thrower->config->relay_count);
|
||||||
|
|
||||||
|
// Publish initial metadata and relay list
|
||||||
|
publish_metadata(thrower);
|
||||||
|
publish_relay_list(thrower);
|
||||||
|
|
||||||
// Publish initial thrower info
|
// Publish initial thrower info
|
||||||
publish_thrower_info(thrower);
|
publish_thrower_info(thrower);
|
||||||
|
|
||||||
|
|||||||
371
plans/deployment_plan.md
Normal file
371
plans/deployment_plan.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
# Superball Thrower Deployment Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document provides a complete deployment plan for the Superball Thrower C implementation on your server (accessible via `sshlt`).
|
||||||
|
|
||||||
|
## Deployment Architecture
|
||||||
|
|
||||||
|
- **Binary Location**: `/usr/local/bin/super_ball_thrower/superball_thrower`
|
||||||
|
- **Config Location**: `/usr/local/bin/super_ball_thrower/config.json`
|
||||||
|
- **Service User**: `superball-thrower`
|
||||||
|
- **Service Name**: `superball-thrower.service`
|
||||||
|
- **Log Location**: `/var/log/superball-thrower/`
|
||||||
|
|
||||||
|
## One-Time Server Setup
|
||||||
|
|
||||||
|
### Step 1: Create setup_server.sh
|
||||||
|
|
||||||
|
Create this file on your local machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# One-time server setup script for Superball Thrower
|
||||||
|
# Run this on the server as root or with sudo
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Superball Thrower Server Setup ==="
|
||||||
|
|
||||||
|
# Create user if it doesn't exist
|
||||||
|
if ! id -u superball-thrower >/dev/null 2>&1; then
|
||||||
|
echo "Creating user superball-thrower..."
|
||||||
|
useradd -r -s /bin/bash -d /usr/local/bin/super_ball_thrower superball-thrower
|
||||||
|
else
|
||||||
|
echo "User superball-thrower already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
echo "Creating directory structure..."
|
||||||
|
mkdir -p /usr/local/bin/super_ball_thrower
|
||||||
|
mkdir -p /var/log/superball-thrower
|
||||||
|
|
||||||
|
# Set ownership
|
||||||
|
echo "Setting ownership..."
|
||||||
|
chown -R superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower
|
||||||
|
chown -R superball-thrower:superball-thrower /var/log/superball-thrower
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
echo "Setting permissions..."
|
||||||
|
chmod 755 /usr/local/bin/super_ball_thrower
|
||||||
|
chmod 755 /var/log/superball-thrower
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Setup Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Copy your config.json to /usr/local/bin/super_ball_thrower/"
|
||||||
|
echo "2. Install the systemd service file"
|
||||||
|
echo "3. Run the deploy_lt.sh script to build and deploy the binary"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create superball-thrower.service
|
||||||
|
|
||||||
|
Create this systemd service file:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Superball Thrower Daemon (C Implementation)
|
||||||
|
Documentation=https://git.laantungir.net/laantungir/super_ball_thrower
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=superball-thrower
|
||||||
|
Group=superball-thrower
|
||||||
|
WorkingDirectory=/usr/local/bin/super_ball_thrower
|
||||||
|
ExecStart=/usr/local/bin/super_ball_thrower/superball_thrower /usr/local/bin/super_ball_thrower/config.json
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=superball-thrower
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/log/superball-thrower /usr/local/bin/super_ball_thrower
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
LockPersonality=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
LimitNOFILE=65536
|
||||||
|
LimitNPROC=4096
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Run Setup Commands
|
||||||
|
|
||||||
|
Execute these commands on the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH into the server
|
||||||
|
sshlt
|
||||||
|
|
||||||
|
# Copy the setup script to the server (or create it there)
|
||||||
|
# Then run it:
|
||||||
|
sudo bash setup_server.sh
|
||||||
|
|
||||||
|
# Install the systemd service file
|
||||||
|
sudo cp superball-thrower.service /etc/systemd/system/
|
||||||
|
sudo chmod 644 /etc/systemd/system/superball-thrower.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable superball-thrower
|
||||||
|
|
||||||
|
# Copy your config.json to the deployment directory
|
||||||
|
sudo cp config.json /usr/local/bin/super_ball_thrower/
|
||||||
|
sudo chown superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower/config.json
|
||||||
|
sudo chmod 600 /usr/local/bin/super_ball_thrower/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Script
|
||||||
|
|
||||||
|
### deploy_lt.sh
|
||||||
|
|
||||||
|
Create this script in your project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Deployment script for Superball Thrower to lt server
|
||||||
|
# This script builds the binary locally and deploys it to the server
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Superball Thrower Deployment Script ==="
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SERVER="sshlt"
|
||||||
|
DEPLOY_DIR="/usr/local/bin/super_ball_thrower"
|
||||||
|
BINARY_NAME="superball_thrower"
|
||||||
|
SERVICE_NAME="superball-thrower"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Step 1: Clean previous build
|
||||||
|
echo -e "${YELLOW}[1/6] Cleaning previous build...${NC}"
|
||||||
|
make clean || true
|
||||||
|
|
||||||
|
# Step 2: Build the project
|
||||||
|
echo -e "${YELLOW}[2/6] Building superball_thrower...${NC}"
|
||||||
|
make
|
||||||
|
|
||||||
|
# Check if build was successful
|
||||||
|
if [ ! -f "$BINARY_NAME" ]; then
|
||||||
|
echo -e "${RED}Error: Build failed - binary not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Build successful!${NC}"
|
||||||
|
|
||||||
|
# Step 3: Stop the service on the server
|
||||||
|
echo -e "${YELLOW}[3/6] Stopping service on server...${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl stop $SERVICE_NAME" || echo "Service not running or doesn't exist yet"
|
||||||
|
|
||||||
|
# Step 4: Deploy binary to server
|
||||||
|
echo -e "${YELLOW}[4/6] Deploying binary to server...${NC}"
|
||||||
|
scp $BINARY_NAME $SERVER:/tmp/$BINARY_NAME
|
||||||
|
|
||||||
|
# Step 5: Move binary to final location with proper permissions
|
||||||
|
echo -e "${YELLOW}[5/6] Installing binary...${NC}"
|
||||||
|
ssh $SERVER "sudo mv /tmp/$BINARY_NAME $DEPLOY_DIR/$BINARY_NAME && \
|
||||||
|
sudo chown superball-thrower:superball-thrower $DEPLOY_DIR/$BINARY_NAME && \
|
||||||
|
sudo chmod 755 $DEPLOY_DIR/$BINARY_NAME"
|
||||||
|
|
||||||
|
# Step 6: Restart the service
|
||||||
|
echo -e "${YELLOW}[6/6] Starting service...${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl start $SERVICE_NAME"
|
||||||
|
|
||||||
|
# Wait a moment for service to start
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Service Status:${NC}"
|
||||||
|
ssh $SERVER "sudo systemctl status $SERVICE_NAME --no-pager" || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}=== Deployment Complete ===${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " View logs: ssh $SERVER 'sudo journalctl -u $SERVICE_NAME -f'"
|
||||||
|
echo " Check status: ssh $SERVER 'sudo systemctl status $SERVICE_NAME'"
|
||||||
|
echo " Restart: ssh $SERVER 'sudo systemctl restart $SERVICE_NAME'"
|
||||||
|
echo " Stop: ssh $SERVER 'sudo systemctl stop $SERVICE_NAME'"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Workflow
|
||||||
|
|
||||||
|
### Initial Deployment
|
||||||
|
|
||||||
|
1. **Prepare the server** (one-time):
|
||||||
|
```bash
|
||||||
|
# Create and run setup_server.sh on the server
|
||||||
|
sshlt
|
||||||
|
# Run the setup commands from Step 3 above
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy the application**:
|
||||||
|
```bash
|
||||||
|
# From your local project directory
|
||||||
|
chmod +x deploy_lt.sh
|
||||||
|
./deploy_lt.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subsequent Deployments
|
||||||
|
|
||||||
|
After making code changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Just run the deployment script
|
||||||
|
./deploy_lt.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Build the binary locally
|
||||||
|
- Stop the service
|
||||||
|
- Deploy the new binary
|
||||||
|
- Restart the service
|
||||||
|
- Show the service status
|
||||||
|
|
||||||
|
## Monitoring and Maintenance
|
||||||
|
|
||||||
|
### View Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Real-time logs
|
||||||
|
ssh sshlt 'sudo journalctl -u superball-thrower -f'
|
||||||
|
|
||||||
|
# Last 100 lines
|
||||||
|
ssh sshlt 'sudo journalctl -u superball-thrower -n 100'
|
||||||
|
|
||||||
|
# Logs since boot
|
||||||
|
ssh sshlt 'sudo journalctl -u superball-thrower -b'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
ssh sshlt 'sudo systemctl status superball-thrower'
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
ssh sshlt 'sudo systemctl restart superball-thrower'
|
||||||
|
|
||||||
|
# Stop service
|
||||||
|
ssh sshlt 'sudo systemctl stop superball-thrower'
|
||||||
|
|
||||||
|
# Start service
|
||||||
|
ssh sshlt 'sudo systemctl start superball-thrower'
|
||||||
|
|
||||||
|
# Disable service (prevent auto-start)
|
||||||
|
ssh sshlt 'sudo systemctl disable superball-thrower'
|
||||||
|
|
||||||
|
# Enable service (auto-start on boot)
|
||||||
|
ssh sshlt 'sudo systemctl enable superball-thrower'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit config on server
|
||||||
|
ssh sshlt 'sudo nano /usr/local/bin/super_ball_thrower/config.json'
|
||||||
|
|
||||||
|
# Or copy from local
|
||||||
|
scp config.json sshlt:/tmp/config.json
|
||||||
|
ssh sshlt 'sudo mv /tmp/config.json /usr/local/bin/super_ball_thrower/config.json && \
|
||||||
|
sudo chown superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower/config.json && \
|
||||||
|
sudo chmod 600 /usr/local/bin/super_ball_thrower/config.json'
|
||||||
|
|
||||||
|
# Restart to apply changes
|
||||||
|
ssh sshlt 'sudo systemctl restart superball-thrower'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Service Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check detailed status
|
||||||
|
ssh sshlt 'sudo systemctl status superball-thrower -l'
|
||||||
|
|
||||||
|
# Check recent logs
|
||||||
|
ssh sshlt 'sudo journalctl -u superball-thrower -n 50'
|
||||||
|
|
||||||
|
# Test binary manually
|
||||||
|
ssh sshlt 'sudo -u superball-thrower /usr/local/bin/super_ball_thrower/superball_thrower /usr/local/bin/super_ball_thrower/config.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fix ownership
|
||||||
|
ssh sshlt 'sudo chown -R superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower'
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
ssh sshlt 'sudo chmod 755 /usr/local/bin/super_ball_thrower && \
|
||||||
|
sudo chmod 755 /usr/local/bin/super_ball_thrower/superball_thrower && \
|
||||||
|
sudo chmod 600 /usr/local/bin/super_ball_thrower/config.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean and rebuild
|
||||||
|
make distclean
|
||||||
|
make
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
cd nostr_core_lib && ./build.sh --nips=1,6,44
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Config File**: Contains private key - ensure it's only readable by superball-thrower user (chmod 600)
|
||||||
|
2. **Service User**: Runs as non-root user with restricted permissions
|
||||||
|
3. **Systemd Hardening**: Service file includes security restrictions
|
||||||
|
4. **Log Access**: Only root and superball-thrower can read logs
|
||||||
|
|
||||||
|
## Backup and Recovery
|
||||||
|
|
||||||
|
### Backup Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup config from server
|
||||||
|
scp sshlt:/usr/local/bin/super_ball_thrower/config.json ./config.backup.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore config to server
|
||||||
|
scp ./config.backup.json sshlt:/tmp/config.json
|
||||||
|
ssh sshlt 'sudo mv /tmp/config.json /usr/local/bin/super_ball_thrower/config.json && \
|
||||||
|
sudo chown superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower/config.json && \
|
||||||
|
sudo chmod 600 /usr/local/bin/super_ball_thrower/config.json && \
|
||||||
|
sudo systemctl restart superball-thrower'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After reviewing this plan:
|
||||||
|
|
||||||
|
1. Switch to Code mode to create the actual script files
|
||||||
|
2. Run the one-time setup on the server
|
||||||
|
3. Test the deployment script
|
||||||
|
4. Monitor the service to ensure it's running correctly
|
||||||
38
setup_server.sh
Executable file
38
setup_server.sh
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# One-time server setup script for Superball Thrower
|
||||||
|
# Run this on the server as root or with sudo
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Superball Thrower Server Setup ==="
|
||||||
|
|
||||||
|
# Create user if it doesn't exist
|
||||||
|
if ! id -u superball-thrower >/dev/null 2>&1; then
|
||||||
|
echo "Creating user superball-thrower..."
|
||||||
|
useradd -r -s /bin/bash -d /usr/local/bin/super_ball_thrower superball-thrower
|
||||||
|
else
|
||||||
|
echo "User superball-thrower already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
echo "Creating directory structure..."
|
||||||
|
mkdir -p /usr/local/bin/super_ball_thrower
|
||||||
|
mkdir -p /var/log/superball-thrower
|
||||||
|
|
||||||
|
# Set ownership
|
||||||
|
echo "Setting ownership..."
|
||||||
|
chown -R superball-thrower:superball-thrower /usr/local/bin/super_ball_thrower
|
||||||
|
chown -R superball-thrower:superball-thrower /var/log/superball-thrower
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
echo "Setting permissions..."
|
||||||
|
chmod 755 /usr/local/bin/super_ball_thrower
|
||||||
|
chmod 755 /var/log/superball-thrower
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Setup Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Copy your config.json to /usr/local/bin/super_ball_thrower/"
|
||||||
|
echo "2. Install the systemd service file"
|
||||||
|
echo "3. Run the deploy_lt.sh script to build and deploy the binary"
|
||||||
41
superball-thrower.service
Normal file
41
superball-thrower.service
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Superball Thrower Daemon (C Implementation)
|
||||||
|
Documentation=https://git.laantungir.net/laantungir/super_ball_thrower
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=superball-thrower
|
||||||
|
Group=superball-thrower
|
||||||
|
WorkingDirectory=/usr/local/bin/super_ball_thrower
|
||||||
|
ExecStart=/usr/local/bin/super_ball_thrower/superball_thrower /usr/local/bin/super_ball_thrower/config.json
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=superball-thrower
|
||||||
|
Environment="LD_LIBRARY_PATH=/usr/local/lib:/usr/lib/x86_64-linux-gnu"
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/log/superball-thrower /usr/local/bin/super_ball_thrower
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
LockPersonality=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
LimitNOFILE=65536
|
||||||
|
LimitNPROC=4096
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
205
tests/QUICKSTART.md
Normal file
205
tests/QUICKSTART.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# Test Suite Quick Start Guide
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Install nak** (Nostr Army Knife):
|
||||||
|
```bash
|
||||||
|
go install github.com/fiatjaf/nak@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Build the daemon**:
|
||||||
|
```bash
|
||||||
|
cd ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Test Run
|
||||||
|
|
||||||
|
### 1. Single Test (No Thrower Required)
|
||||||
|
|
||||||
|
Test the framework itself:
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Single-Hop Test (1 Thrower)
|
||||||
|
|
||||||
|
**Setup Thrower A:**
|
||||||
|
```bash
|
||||||
|
# In terminal 1 - Create config
|
||||||
|
cd ..
|
||||||
|
cat > config_test_a.json <<EOF
|
||||||
|
{
|
||||||
|
"thrower": {
|
||||||
|
"privateKey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"name": "Test Thrower A",
|
||||||
|
"description": "Test thrower for single-hop tests",
|
||||||
|
"maxDelay": 86460,
|
||||||
|
"refreshRate": 300,
|
||||||
|
"supportedSups": "1,2,3,4,5,6",
|
||||||
|
"software": "https://git.laantungir.net/laantungir/super_ball_thrower.git",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"url": "wss://relay.laantungir.net",
|
||||||
|
"read": true,
|
||||||
|
"write": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"daemon": {
|
||||||
|
"logLevel": "info",
|
||||||
|
"maxQueueSize": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Start thrower
|
||||||
|
./superball_thrower config_test_a.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run Test:**
|
||||||
|
```bash
|
||||||
|
# In terminal 2
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh test_single_hop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Multi-Hop Test (3 Throwers)
|
||||||
|
|
||||||
|
**Setup Thrower A, B, C:**
|
||||||
|
```bash
|
||||||
|
# Terminal 1 - Thrower A
|
||||||
|
cd ..
|
||||||
|
./superball_thrower config_test_a.json
|
||||||
|
|
||||||
|
# Terminal 2 - Thrower B (create config first)
|
||||||
|
cat > config_test_b.json <<EOF
|
||||||
|
{
|
||||||
|
"thrower": {
|
||||||
|
"privateKey": "0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"name": "Test Thrower B",
|
||||||
|
"description": "Test thrower B",
|
||||||
|
"maxDelay": 86460,
|
||||||
|
"refreshRate": 300,
|
||||||
|
"supportedSups": "1,2,3,4,5,6",
|
||||||
|
"software": "https://git.laantungir.net/laantungir/super_ball_thrower.git",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"url": "wss://relay.damus.io",
|
||||||
|
"read": true,
|
||||||
|
"write": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"daemon": {
|
||||||
|
"logLevel": "info",
|
||||||
|
"maxQueueSize": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
./superball_thrower config_test_b.json
|
||||||
|
|
||||||
|
# Terminal 3 - Thrower C (create config first)
|
||||||
|
cat > config_test_c.json <<EOF
|
||||||
|
{
|
||||||
|
"thrower": {
|
||||||
|
"privateKey": "0000000000000000000000000000000000000000000000000000000000000004",
|
||||||
|
"name": "Test Thrower C",
|
||||||
|
"description": "Test thrower C",
|
||||||
|
"maxDelay": 86460,
|
||||||
|
"refreshRate": 300,
|
||||||
|
"supportedSups": "1,2,3,4,5,6",
|
||||||
|
"software": "https://git.laantungir.net/laantungir/super_ball_thrower.git",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"url": "wss://nos.lol",
|
||||||
|
"read": true,
|
||||||
|
"write": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"daemon": {
|
||||||
|
"logLevel": "info",
|
||||||
|
"maxQueueSize": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
./superball_thrower config_test_c.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run Tests:**
|
||||||
|
```bash
|
||||||
|
# Terminal 4
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh test_multi_hop
|
||||||
|
./test_framework.sh test_padding
|
||||||
|
./test_framework.sh test_end_to_end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run All Tests
|
||||||
|
|
||||||
|
With all 3 throwers running:
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Timing
|
||||||
|
|
||||||
|
All tests use 2-second delays for fast execution:
|
||||||
|
- **test_single_hop**: ~10 seconds
|
||||||
|
- **test_multi_hop**: ~15 seconds
|
||||||
|
- **test_padding**: ~12 seconds
|
||||||
|
- **test_delays**: ~20 seconds (tests multiple delay values)
|
||||||
|
- **test_thrower_info**: ~10 seconds
|
||||||
|
- **test_relay_auth**: ~5 seconds (skipped if no AUTH relay)
|
||||||
|
- **test_end_to_end**: ~15 seconds
|
||||||
|
|
||||||
|
**Total suite runtime**: ~1-2 minutes (with all throwers running)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "nak command not found"
|
||||||
|
```bash
|
||||||
|
go install github.com/fiatjaf/nak@latest
|
||||||
|
export PATH=$PATH:$(go env GOPATH)/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Event not found on relay"
|
||||||
|
- Check thrower is running: `ps aux | grep superball_thrower`
|
||||||
|
- Check thrower logs for errors
|
||||||
|
- Verify relay connectivity: `nak req --relay wss://relay.laantungir.net -k 1 --limit 1`
|
||||||
|
|
||||||
|
### "Event arrived too early"
|
||||||
|
- Thrower may not be respecting delays
|
||||||
|
- Check queue processing in logs
|
||||||
|
- Verify system time is correct
|
||||||
|
|
||||||
|
### Tests timeout
|
||||||
|
- Increase timeout values in test scripts
|
||||||
|
- Check network connectivity
|
||||||
|
- Verify relays are responding
|
||||||
|
|
||||||
|
## Test Keys
|
||||||
|
|
||||||
|
⚠️ **WARNING**: Test keys in `fixtures/test_keys.json` are for testing only!
|
||||||
|
|
||||||
|
- Builder: `0000...0001`
|
||||||
|
- Thrower A: `0000...0002`
|
||||||
|
- Thrower B: `0000...0003`
|
||||||
|
- Thrower C: `0000...0004`
|
||||||
|
|
||||||
|
**NEVER use these keys in production!**
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Run individual tests to verify each SUP
|
||||||
|
2. Run full suite with `./test_framework.sh all`
|
||||||
|
3. Check logs in `tests/logs/` for details
|
||||||
|
4. Review results in `tests/results/summary.txt`
|
||||||
|
|
||||||
|
For detailed documentation, see [README.md](README.md).
|
||||||
376
tests/README.md
Normal file
376
tests/README.md
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
# Superball Protocol Test Suite
|
||||||
|
|
||||||
|
Comprehensive test suite for verifying SUP-01 through SUP-06 compliance in the Superball Thrower daemon.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This test suite validates all aspects of the Superball protocol implementation:
|
||||||
|
|
||||||
|
- **SUP-01**: Basic single-hop routing
|
||||||
|
- **SUP-02**: Multi-hop routing (2-5 hops)
|
||||||
|
- **SUP-03**: Padding payload handling (Type 2 payloads)
|
||||||
|
- **SUP-04**: Delay and jitter verification
|
||||||
|
- **SUP-05**: Relay authentication testing
|
||||||
|
- **SUP-06**: Thrower information publishing
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Required Tools
|
||||||
|
|
||||||
|
1. **nak** (Nostr Army Knife)
|
||||||
|
```bash
|
||||||
|
go install github.com/fiatjaf/nak@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **jq** (JSON processor)
|
||||||
|
```bash
|
||||||
|
sudo apt-get install jq # Debian/Ubuntu
|
||||||
|
brew install jq # macOS
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Superball Thrower daemon**
|
||||||
|
```bash
|
||||||
|
cd ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Throwers
|
||||||
|
|
||||||
|
For multi-hop tests, you need multiple thrower instances running. Create separate config files for each:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Thrower A
|
||||||
|
cp config.example.json config_thrower_a.json
|
||||||
|
# Edit config_thrower_a.json with thrower_a private key
|
||||||
|
|
||||||
|
# Thrower B
|
||||||
|
cp config.example.json config_thrower_b.json
|
||||||
|
# Edit config_thrower_b.json with thrower_b private key
|
||||||
|
|
||||||
|
# Thrower C
|
||||||
|
cp config.example.json config_thrower_c.json
|
||||||
|
# Edit config_thrower_c.json with thrower_c private key
|
||||||
|
```
|
||||||
|
|
||||||
|
Start each thrower in a separate terminal:
|
||||||
|
```bash
|
||||||
|
./superball_thrower config_thrower_a.json
|
||||||
|
./superball_thrower config_thrower_b.json
|
||||||
|
./superball_thrower config_thrower_c.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── test_framework.sh # Main test orchestrator
|
||||||
|
├── test_single_hop.sh # SUP-01: Single-hop routing
|
||||||
|
├── test_multi_hop.sh # SUP-02: Multi-hop routing
|
||||||
|
├── test_padding.sh # SUP-03: Padding payloads
|
||||||
|
├── test_delays.sh # SUP-04: Delay verification
|
||||||
|
├── test_relay_auth.sh # SUP-05: AUTH relay handling
|
||||||
|
├── test_thrower_info.sh # SUP-06: Thrower info publishing
|
||||||
|
├── test_end_to_end.sh # Complete workflow test
|
||||||
|
├── helpers/
|
||||||
|
│ ├── timing_utils.sh # Timing and delay utilities
|
||||||
|
│ └── event_utils.sh # Event creation utilities
|
||||||
|
├── fixtures/
|
||||||
|
│ ├── test_keys.json # Test keypairs
|
||||||
|
│ └── test_relays.json # Test relay configurations
|
||||||
|
├── logs/ # Test execution logs
|
||||||
|
└── results/ # Test results
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Run All Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Specific Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh test_single_hop
|
||||||
|
./test_framework.sh test_multi_hop
|
||||||
|
./test_framework.sh test_padding
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Available Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh list
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Help
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Descriptions
|
||||||
|
|
||||||
|
### test_single_hop.sh (SUP-01)
|
||||||
|
|
||||||
|
Tests basic single-hop routing:
|
||||||
|
- Builder creates encrypted event
|
||||||
|
- Thrower receives and decrypts
|
||||||
|
- Thrower waits for delay
|
||||||
|
- Thrower posts to final relay
|
||||||
|
- Verifies timing and content
|
||||||
|
|
||||||
|
**Duration**: ~10-15 seconds
|
||||||
|
**Requirements**: 1 thrower running
|
||||||
|
|
||||||
|
### test_multi_hop.sh (SUP-02)
|
||||||
|
|
||||||
|
Tests multi-hop routing with 3 hops:
|
||||||
|
- Creates onion-routed event (3 layers)
|
||||||
|
- Each thrower unwraps one layer
|
||||||
|
- Verifies complete routing chain
|
||||||
|
- Checks cumulative delays
|
||||||
|
|
||||||
|
**Duration**: ~30-45 seconds
|
||||||
|
**Requirements**: 3 throwers running (A, B, C)
|
||||||
|
|
||||||
|
### test_padding.sh (SUP-03)
|
||||||
|
|
||||||
|
Tests padding payload handling:
|
||||||
|
- Thrower A adds padding bytes
|
||||||
|
- Creates Type 2 payload
|
||||||
|
- Thrower B performs double decryption
|
||||||
|
- Verifies padding is discarded
|
||||||
|
- Confirms content integrity
|
||||||
|
|
||||||
|
**Duration**: ~15-20 seconds
|
||||||
|
**Requirements**: 2 throwers running (A, B)
|
||||||
|
|
||||||
|
### test_delays.sh (SUP-04)
|
||||||
|
|
||||||
|
Tests delay constraints with multiple values:
|
||||||
|
- Tests delays: 0s, 5s, 10s, 30s, 60s
|
||||||
|
- Verifies minimum delay respected
|
||||||
|
- Checks jitter application
|
||||||
|
- Validates timing accuracy
|
||||||
|
|
||||||
|
**Duration**: ~2-3 minutes
|
||||||
|
**Requirements**: 1 thrower running
|
||||||
|
|
||||||
|
### test_relay_auth.sh (SUP-05)
|
||||||
|
|
||||||
|
Tests AUTH-required relay handling:
|
||||||
|
- Detects AUTH requirement
|
||||||
|
- Verifies relay marked as "auth-required"
|
||||||
|
- Confirms events skip AUTH relay
|
||||||
|
- Checks appropriate logging
|
||||||
|
|
||||||
|
**Duration**: ~5 seconds
|
||||||
|
**Requirements**: AUTH-required relay (optional)
|
||||||
|
|
||||||
|
### test_thrower_info.sh (SUP-06)
|
||||||
|
|
||||||
|
Tests thrower information publishing:
|
||||||
|
- Queries for kind 12222 events
|
||||||
|
- Verifies all required fields
|
||||||
|
- Checks relay configurations
|
||||||
|
- Validates event structure
|
||||||
|
|
||||||
|
**Duration**: ~10-15 seconds
|
||||||
|
**Requirements**: 1 thrower running with auto-publish
|
||||||
|
|
||||||
|
### test_end_to_end.sh
|
||||||
|
|
||||||
|
Complete workflow test combining all features:
|
||||||
|
- 3-hop routing chain
|
||||||
|
- Padding at first hop
|
||||||
|
- Delays at each hop
|
||||||
|
- Full protocol compliance
|
||||||
|
- Performance metrics
|
||||||
|
|
||||||
|
**Duration**: ~30-45 seconds
|
||||||
|
**Requirements**: 3 throwers running (A, B, C)
|
||||||
|
|
||||||
|
## Test Configuration
|
||||||
|
|
||||||
|
### Test Keys
|
||||||
|
|
||||||
|
Test keypairs are defined in `fixtures/test_keys.json`:
|
||||||
|
- **builder**: Creates initial Superball events
|
||||||
|
- **thrower_a**: First hop
|
||||||
|
- **thrower_b**: Second hop
|
||||||
|
- **thrower_c**: Third hop
|
||||||
|
- **thrower_d**: Fourth hop (for 4-hop tests)
|
||||||
|
- **thrower_e**: Fifth hop (for 5-hop tests)
|
||||||
|
|
||||||
|
⚠️ **WARNING**: These are test keys only. NEVER use in production!
|
||||||
|
|
||||||
|
### Test Relays
|
||||||
|
|
||||||
|
Relay configurations are in `fixtures/test_relays.json`:
|
||||||
|
- **primary**: wss://relay.laantungir.net
|
||||||
|
- **secondary**: wss://relay.damus.io
|
||||||
|
- **tertiary**: wss://nos.lol
|
||||||
|
- **final**: wss://relay.nostr.band
|
||||||
|
|
||||||
|
You can modify these to use your preferred relays.
|
||||||
|
|
||||||
|
## Interpreting Results
|
||||||
|
|
||||||
|
### Success Output
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Superball Protocol Test Suite ===
|
||||||
|
Starting at Mon Dec 10 14:00:00 UTC 2025
|
||||||
|
|
||||||
|
Running: test_single_hop
|
||||||
|
✓ PASSED: test_single_hop (12s)
|
||||||
|
|
||||||
|
Running: test_multi_hop
|
||||||
|
✓ PASSED: test_multi_hop (38s)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
=== Test Summary ===
|
||||||
|
Passed: 7
|
||||||
|
Failed: 0
|
||||||
|
Skipped: 0
|
||||||
|
Total: 7
|
||||||
|
```
|
||||||
|
|
||||||
|
### Failure Output
|
||||||
|
|
||||||
|
When a test fails, the last 20 lines of the log are displayed:
|
||||||
|
|
||||||
|
```
|
||||||
|
Running: test_single_hop
|
||||||
|
✗ FAILED: test_single_hop (15s)
|
||||||
|
See log: logs/test_single_hop.log
|
||||||
|
Last 20 lines of log:
|
||||||
|
ERROR: Inner event not found on final relay within 30s
|
||||||
|
This could indicate:
|
||||||
|
- Thrower is not running
|
||||||
|
- Network connectivity issues
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
Detailed logs for each test are saved in `logs/`:
|
||||||
|
- `logs/test_single_hop.log`
|
||||||
|
- `logs/test_multi_hop.log`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
Results summary is saved in `results/summary.txt`.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Test Fails: "nak command not found"
|
||||||
|
|
||||||
|
Install nak:
|
||||||
|
```bash
|
||||||
|
go install github.com/fiatjaf/nak@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Fails: "superball_thrower binary not found"
|
||||||
|
|
||||||
|
Build the daemon:
|
||||||
|
```bash
|
||||||
|
cd ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Fails: "Event not found on relay"
|
||||||
|
|
||||||
|
Possible causes:
|
||||||
|
1. Thrower not running
|
||||||
|
2. Wrong private key in config
|
||||||
|
3. Relay connectivity issues
|
||||||
|
4. Firewall blocking WebSocket connections
|
||||||
|
|
||||||
|
Check thrower logs:
|
||||||
|
```bash
|
||||||
|
tail -f /tmp/superball_thrower.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Fails: "Event arrived too early"
|
||||||
|
|
||||||
|
The thrower may not be respecting delay constraints. Check:
|
||||||
|
1. Queue processing is working
|
||||||
|
2. Delays are being applied
|
||||||
|
3. System time is correct
|
||||||
|
|
||||||
|
### Multi-hop Tests Fail
|
||||||
|
|
||||||
|
Ensure all required throwers are running:
|
||||||
|
```bash
|
||||||
|
ps aux | grep superball_thrower
|
||||||
|
```
|
||||||
|
|
||||||
|
Each thrower needs its own config file with the correct private key.
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
Example workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Superball Tests
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y jq libsecp256k1-dev libsodium-dev
|
||||||
|
go install github.com/fiatjaf/nak@latest
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: make
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh all
|
||||||
|
|
||||||
|
- name: Upload results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: tests/results/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When adding new tests:
|
||||||
|
|
||||||
|
1. Create test script in `tests/`
|
||||||
|
2. Follow naming convention: `test_<feature>.sh`
|
||||||
|
3. Add to `TESTS` array in `test_framework.sh`
|
||||||
|
4. Include clear documentation
|
||||||
|
5. Add expected duration and requirements
|
||||||
|
6. Provide troubleshooting guidance
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Same as parent project.
|
||||||
198
tests/TEST_SUITE_SUMMARY.md
Normal file
198
tests/TEST_SUITE_SUMMARY.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Superball Protocol Test Suite - Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Comprehensive test suite for validating SUP-01 through SUP-06 compliance in the C implementation of the Superball Thrower daemon.
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
| Test | SUP | Feature | Duration | Throwers Required |
|
||||||
|
|------|-----|---------|----------|-------------------|
|
||||||
|
| `test_single_hop.sh` | SUP-01 | Basic routing | ~10s | 1 (A) |
|
||||||
|
| `test_multi_hop.sh` | SUP-02 | Multi-hop (3 hops) | ~15s | 3 (A, B, C) |
|
||||||
|
| `test_padding.sh` | SUP-03 | Padding payloads | ~12s | 2 (A, B) |
|
||||||
|
| `test_delays.sh` | SUP-04 | Delay verification | ~20s | 1 (A) |
|
||||||
|
| `test_relay_auth.sh` | SUP-05 | AUTH handling | ~5s | Optional |
|
||||||
|
| `test_thrower_info.sh` | SUP-06 | Thrower info | ~10s | 1 (A) |
|
||||||
|
| `test_end_to_end.sh` | All | Complete workflow | ~15s | 3 (A, B, C) |
|
||||||
|
|
||||||
|
**Total Runtime**: ~1-2 minutes (with all throwers running)
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── test_framework.sh # Main orchestrator (300 lines)
|
||||||
|
├── test_single_hop.sh # SUP-01 test (100 lines)
|
||||||
|
├── test_multi_hop.sh # SUP-02 test (150 lines)
|
||||||
|
├── test_padding.sh # SUP-03 test (140 lines)
|
||||||
|
├── test_delays.sh # SUP-04 test (100 lines)
|
||||||
|
├── test_relay_auth.sh # SUP-05 test (80 lines)
|
||||||
|
├── test_thrower_info.sh # SUP-06 test (150 lines)
|
||||||
|
├── test_end_to_end.sh # Integration test (200 lines)
|
||||||
|
├── helpers/
|
||||||
|
│ ├── timing_utils.sh # Timing utilities (60 lines)
|
||||||
|
│ └── event_utils.sh # Event utilities (120 lines)
|
||||||
|
├── fixtures/
|
||||||
|
│ ├── test_keys.json # Test keypairs
|
||||||
|
│ └── test_relays.json # Test relay configs
|
||||||
|
├── logs/ # Test execution logs (generated)
|
||||||
|
├── results/ # Test results (generated)
|
||||||
|
├── README.md # Full documentation
|
||||||
|
├── QUICKSTART.md # Quick start guide
|
||||||
|
└── TEST_SUITE_SUMMARY.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total Lines of Code**: ~1,400 lines
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all tests
|
||||||
|
./test_framework.sh list
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
./test_framework.sh all
|
||||||
|
|
||||||
|
# Run specific test
|
||||||
|
./test_framework.sh test_single_hop
|
||||||
|
|
||||||
|
# View help
|
||||||
|
./test_framework.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Parameters
|
||||||
|
|
||||||
|
All tests use **2-second delays** for fast execution:
|
||||||
|
- Single-hop: 2s delay
|
||||||
|
- Multi-hop: 2s per hop (6s total for 3 hops)
|
||||||
|
- Padding: 2s per hop
|
||||||
|
- End-to-end: 2s per hop (6s total)
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
Each test verifies:
|
||||||
|
1. ✓ Event routing completes successfully
|
||||||
|
2. ✓ Timing constraints respected (delay >= specified)
|
||||||
|
3. ✓ Content integrity maintained
|
||||||
|
4. ✓ Event structure valid
|
||||||
|
5. ✓ Protocol compliance (SUP-specific)
|
||||||
|
|
||||||
|
## Test Keys (fixtures/test_keys.json)
|
||||||
|
|
||||||
|
⚠️ **FOR TESTING ONLY - DO NOT USE IN PRODUCTION**
|
||||||
|
|
||||||
|
| Role | Private Key | Public Key |
|
||||||
|
|------|-------------|------------|
|
||||||
|
| Builder | `0000...0001` | `79be667e...` |
|
||||||
|
| Thrower A | `0000...0002` | `c6047f94...` |
|
||||||
|
| Thrower B | `0000...0003` | `f9308a01...` |
|
||||||
|
| Thrower C | `0000...0004` | `e493dbf1...` |
|
||||||
|
| Thrower D | `0000...0005` | `2f8bde4d...` |
|
||||||
|
| Thrower E | `0000...0006` | `fff97bd5...` |
|
||||||
|
|
||||||
|
## Test Relays (fixtures/test_relays.json)
|
||||||
|
|
||||||
|
Default test relays:
|
||||||
|
- **Primary**: wss://relay.laantungir.net
|
||||||
|
- **Secondary**: wss://relay.damus.io
|
||||||
|
- **Tertiary**: wss://nos.lol
|
||||||
|
- **Final**: wss://relay.nostr.band
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
1. **nak** (Nostr Army Knife) - Event creation and relay interaction
|
||||||
|
2. **jq** - JSON processing
|
||||||
|
3. **bash** - Shell scripting
|
||||||
|
4. **superball_thrower** - The daemon being tested
|
||||||
|
|
||||||
|
## Output Examples
|
||||||
|
|
||||||
|
### Successful Test
|
||||||
|
```
|
||||||
|
=== SUP-01: Single-Hop Routing Test ===
|
||||||
|
Builder: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
|
||||||
|
Thrower A: c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
|
||||||
|
...
|
||||||
|
=== TEST PASSED ===
|
||||||
|
✓ Single-hop routing successful
|
||||||
|
✓ Delay constraint respected (3s >= 2s)
|
||||||
|
✓ Event content preserved
|
||||||
|
✓ Event published to correct relay
|
||||||
|
```
|
||||||
|
|
||||||
|
### Failed Test
|
||||||
|
```
|
||||||
|
=== SUP-01: Single-Hop Routing Test ===
|
||||||
|
...
|
||||||
|
ERROR: Inner event not found on final relay within 30s
|
||||||
|
This could indicate:
|
||||||
|
- Thrower is not running
|
||||||
|
- Network connectivity issues
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with CI/CD
|
||||||
|
|
||||||
|
The test suite is designed for easy CI/CD integration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/test.yml
|
||||||
|
- name: Run Superball Tests
|
||||||
|
run: |
|
||||||
|
cd tests
|
||||||
|
./test_framework.sh all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting Guide
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| "nak not found" | `go install github.com/fiatjaf/nak@latest` |
|
||||||
|
| "Event not found" | Check thrower is running, verify relay connectivity |
|
||||||
|
| "Event too early" | Check delay implementation in daemon |
|
||||||
|
| "Timeout" | Increase timeout values, check network |
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential additions:
|
||||||
|
- [ ] 4-hop and 5-hop routing tests
|
||||||
|
- [ ] Stress testing (high volume)
|
||||||
|
- [ ] Performance benchmarking
|
||||||
|
- [ ] Failure injection tests
|
||||||
|
- [ ] Network partition simulation
|
||||||
|
- [ ] Concurrent routing tests
|
||||||
|
- [ ] Memory leak detection
|
||||||
|
- [ ] Relay failover testing
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
Test suite provides:
|
||||||
|
- **Coverage**: All 6 SUPs tested
|
||||||
|
- **Execution Time**: < 2 minutes
|
||||||
|
- **Automation**: Fully automated
|
||||||
|
- **Reporting**: Detailed logs and summaries
|
||||||
|
- **Reliability**: Deterministic results
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **README.md**: Complete documentation
|
||||||
|
- **QUICKSTART.md**: Quick start guide
|
||||||
|
- **TEST_SUITE_SUMMARY.md**: This summary
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
Test suite is self-contained and requires minimal maintenance:
|
||||||
|
- Update relay URLs if relays change
|
||||||
|
- Adjust timeouts if network conditions change
|
||||||
|
- Add new tests for new SUPs
|
||||||
|
- Update test keys if needed (testing only)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Same as parent project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-12-10
|
||||||
|
**Version**: 1.0
|
||||||
|
**Author**: Roo (Code Mode)
|
||||||
34
tests/fixtures/test_keys.json
vendored
Normal file
34
tests/fixtures/test_keys.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"description": "Test keypairs for Superball protocol testing",
|
||||||
|
"builder": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
"description": "Builder - creates initial Superball events"
|
||||||
|
},
|
||||||
|
"thrower_a": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"pubkey": "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
|
||||||
|
"description": "Thrower A - first hop in routing chain"
|
||||||
|
},
|
||||||
|
"thrower_b": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"pubkey": "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
|
||||||
|
"description": "Thrower B - second hop in routing chain"
|
||||||
|
},
|
||||||
|
"thrower_c": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000004",
|
||||||
|
"pubkey": "e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13",
|
||||||
|
"description": "Thrower C - third hop in routing chain"
|
||||||
|
},
|
||||||
|
"thrower_d": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000005",
|
||||||
|
"pubkey": "2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4",
|
||||||
|
"description": "Thrower D - fourth hop in routing chain"
|
||||||
|
},
|
||||||
|
"thrower_e": {
|
||||||
|
"privkey": "0000000000000000000000000000000000000000000000000000000000000006",
|
||||||
|
"pubkey": "fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
|
||||||
|
"description": "Thrower E - fifth hop in routing chain"
|
||||||
|
},
|
||||||
|
"note": "These are test keys only. NEVER use in production!"
|
||||||
|
}
|
||||||
46
tests/fixtures/test_relays.json
vendored
Normal file
46
tests/fixtures/test_relays.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"description": "Test relay configurations for Superball protocol testing",
|
||||||
|
"relays": {
|
||||||
|
"primary": {
|
||||||
|
"url": "wss://relay.laantungir.net",
|
||||||
|
"read": true,
|
||||||
|
"write": true,
|
||||||
|
"description": "Primary test relay"
|
||||||
|
},
|
||||||
|
"secondary": {
|
||||||
|
"url": "wss://relay.damus.io",
|
||||||
|
"read": true,
|
||||||
|
"write": true,
|
||||||
|
"description": "Secondary test relay"
|
||||||
|
},
|
||||||
|
"tertiary": {
|
||||||
|
"url": "wss://nos.lol",
|
||||||
|
"read": true,
|
||||||
|
"write": true,
|
||||||
|
"description": "Tertiary test relay"
|
||||||
|
},
|
||||||
|
"final": {
|
||||||
|
"url": "wss://relay.nostr.band",
|
||||||
|
"read": true,
|
||||||
|
"write": true,
|
||||||
|
"description": "Final destination relay for testing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test_scenarios": {
|
||||||
|
"single_hop": {
|
||||||
|
"thrower_relay": "wss://relay.laantungir.net",
|
||||||
|
"final_relay": "wss://relay.damus.io"
|
||||||
|
},
|
||||||
|
"multi_hop_2": {
|
||||||
|
"thrower_a_relay": "wss://relay.laantungir.net",
|
||||||
|
"thrower_b_relay": "wss://relay.damus.io",
|
||||||
|
"final_relay": "wss://nos.lol"
|
||||||
|
},
|
||||||
|
"multi_hop_3": {
|
||||||
|
"thrower_a_relay": "wss://relay.laantungir.net",
|
||||||
|
"thrower_b_relay": "wss://relay.damus.io",
|
||||||
|
"thrower_c_relay": "wss://nos.lol",
|
||||||
|
"final_relay": "wss://relay.nostr.band"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
tests/helpers/event_utils.sh
Executable file
151
tests/helpers/event_utils.sh
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Event Utilities for Superball Protocol Tests
|
||||||
|
|
||||||
|
# Create a test event (kind 1 text note)
|
||||||
|
create_test_event() {
|
||||||
|
local privkey=$1
|
||||||
|
local content=$2
|
||||||
|
local timestamp=${3:-$(date +%s)}
|
||||||
|
|
||||||
|
echo "{\"content\":\"$content\",\"created_at\":$timestamp}" | \
|
||||||
|
nak event --sec "$privkey" -k 1 --tag ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create routing payload (Type 1)
|
||||||
|
create_routing_payload() {
|
||||||
|
local event_json=$1
|
||||||
|
local relays=$2 # Comma-separated relay URLs
|
||||||
|
local delay=$3
|
||||||
|
local next_hop_pubkey=${4:-null}
|
||||||
|
local audit_tag=$5
|
||||||
|
local add_padding_bytes=${6:-0}
|
||||||
|
|
||||||
|
# Convert relays to JSON array
|
||||||
|
local relay_array="["
|
||||||
|
IFS=',' read -ra RELAY_ARRAY <<< "$relays"
|
||||||
|
for i in "${!RELAY_ARRAY[@]}"; do
|
||||||
|
if [ $i -gt 0 ]; then
|
||||||
|
relay_array+=","
|
||||||
|
fi
|
||||||
|
relay_array+="\"${RELAY_ARRAY[$i]}\""
|
||||||
|
done
|
||||||
|
relay_array+="]"
|
||||||
|
|
||||||
|
# Build routing payload
|
||||||
|
local routing_payload=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"event": $event_json,
|
||||||
|
"relays": $relay_array,
|
||||||
|
"delay": $delay,
|
||||||
|
"next_hop_pubkey": $([ "$next_hop_pubkey" = "null" ] && echo "null" || echo "\"$next_hop_pubkey\""),
|
||||||
|
"audit_tag": "$audit_tag"
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ $add_padding_bytes -gt 0 ]; then
|
||||||
|
routing_payload+=",\n \"add_padding_bytes\": $add_padding_bytes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
routing_payload+=$'\n}'
|
||||||
|
|
||||||
|
echo "$routing_payload"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create padding payload (Type 2)
|
||||||
|
create_padding_payload() {
|
||||||
|
local event_json=$1
|
||||||
|
local padding_bytes=$2
|
||||||
|
|
||||||
|
# Generate random padding
|
||||||
|
local padding=$(head -c "$padding_bytes" /dev/urandom | base64 -w 0)
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"event": $event_json,
|
||||||
|
"padding": "$padding"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Encrypt payload with NIP-44
|
||||||
|
encrypt_payload() {
|
||||||
|
local sender_privkey=$1
|
||||||
|
local recipient_pubkey=$2
|
||||||
|
local payload=$3
|
||||||
|
|
||||||
|
echo "$payload" | nak encrypt --sec "$sender_privkey" --recipient-pubkey "$recipient_pubkey"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create kind 22222 routing event
|
||||||
|
create_routing_event() {
|
||||||
|
local sender_privkey=$1
|
||||||
|
local recipient_pubkey=$2
|
||||||
|
local encrypted_content=$3
|
||||||
|
local timestamp=${4:-$(date +%s)}
|
||||||
|
|
||||||
|
# Create event with p-tag for recipient
|
||||||
|
nak event --sec "$sender_privkey" -k 22222 \
|
||||||
|
--tag "p,$recipient_pubkey" \
|
||||||
|
--content "$encrypted_content" \
|
||||||
|
--created-at "$timestamp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract event ID from event JSON
|
||||||
|
get_event_id() {
|
||||||
|
local event_json=$1
|
||||||
|
echo "$event_json" | jq -r '.id'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract pubkey from event JSON
|
||||||
|
get_event_pubkey() {
|
||||||
|
local event_json=$1
|
||||||
|
echo "$event_json" | jq -r '.pubkey'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract content from event JSON
|
||||||
|
get_event_content() {
|
||||||
|
local event_json=$1
|
||||||
|
echo "$event_json" | jq -r '.content'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify event signature
|
||||||
|
verify_event_signature() {
|
||||||
|
local event_json=$1
|
||||||
|
|
||||||
|
# Use nak to verify (it will exit with error if invalid)
|
||||||
|
echo "$event_json" | nak verify 2>/dev/null
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
# Publish event to relay
|
||||||
|
publish_event() {
|
||||||
|
local event_json=$1
|
||||||
|
local relay=$2
|
||||||
|
|
||||||
|
echo "$event_json" | nak event "$relay" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Query event from relay
|
||||||
|
query_event() {
|
||||||
|
local event_id=$1
|
||||||
|
local relay=$2
|
||||||
|
local timeout=${3:-5}
|
||||||
|
|
||||||
|
nak req --relay "$relay" -i "$event_id" --timeout "$timeout" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Monitor relay for specific event kind
|
||||||
|
monitor_relay_for_kind() {
|
||||||
|
local relay=$1
|
||||||
|
local kind=$2
|
||||||
|
local timeout=${3:-30}
|
||||||
|
|
||||||
|
timeout "$timeout" nak req --stream --relay "$relay" -k "$kind" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get pubkey from private key
|
||||||
|
get_pubkey_from_privkey() {
|
||||||
|
local privkey=$1
|
||||||
|
nak key public "$privkey"
|
||||||
|
}
|
||||||
70
tests/helpers/timing_utils.sh
Executable file
70
tests/helpers/timing_utils.sh
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Timing Utilities for Superball Protocol Tests
|
||||||
|
|
||||||
|
# Get current timestamp in seconds
|
||||||
|
get_timestamp() {
|
||||||
|
date +%s
|
||||||
|
}
|
||||||
|
|
||||||
|
# Measure delay between two timestamps
|
||||||
|
measure_delay() {
|
||||||
|
local start_time=$1
|
||||||
|
local end_time=$2
|
||||||
|
echo $((end_time - start_time))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify delay is within acceptable range (with jitter tolerance)
|
||||||
|
verify_delay() {
|
||||||
|
local expected=$1
|
||||||
|
local actual=$2
|
||||||
|
local tolerance=${3:-10} # Default 10% tolerance for jitter
|
||||||
|
|
||||||
|
local min=$((expected - expected * tolerance / 100))
|
||||||
|
local max=$((expected + expected * tolerance / 100))
|
||||||
|
|
||||||
|
if [ $actual -ge $min ] && [ $actual -le $max ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Delay verification failed: expected ${expected}s (±${tolerance}%), got ${actual}s"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for event with timeout
|
||||||
|
wait_for_event() {
|
||||||
|
local event_id=$1
|
||||||
|
local relay=$2
|
||||||
|
local timeout=${3:-30}
|
||||||
|
local start_time=$(get_timestamp)
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
local current_time=$(get_timestamp)
|
||||||
|
local elapsed=$((current_time - start_time))
|
||||||
|
|
||||||
|
if [ $elapsed -ge $timeout ]; then
|
||||||
|
echo "Timeout waiting for event $event_id"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if event exists on relay
|
||||||
|
if nak req --relay "$relay" -i "$event_id" --timeout 2 2>/dev/null | grep -q "$event_id"; then
|
||||||
|
echo $current_time
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate jitter percentage
|
||||||
|
calculate_jitter() {
|
||||||
|
local expected=$1
|
||||||
|
local actual=$2
|
||||||
|
|
||||||
|
local diff=$((actual - expected))
|
||||||
|
local abs_diff=${diff#-} # Absolute value
|
||||||
|
local jitter_pct=$((abs_diff * 100 / expected))
|
||||||
|
|
||||||
|
echo $jitter_pct
|
||||||
|
}
|
||||||
140
tests/test_delays.sh
Executable file
140
tests/test_delays.sh
Executable file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-04: Delay and Jitter Verification
|
||||||
|
# Tests: Timing requirements and jitter application
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_RELAY=$(jq -r '.test_scenarios.single_hop.thrower_relay' "$RELAYS_FILE")
|
||||||
|
FINAL_RELAY=$(jq -r '.test_scenarios.single_hop.final_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "=== SUP-04: Delay and Jitter Test ==="
|
||||||
|
echo "Testing multiple delay values with jitter verification"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test different delay values
|
||||||
|
DELAY_VALUES=(0 2 2 2 2)
|
||||||
|
AUDIT_TAG_BASE="test-delays-$(date +%s)"
|
||||||
|
|
||||||
|
PASSED=0
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
for DELAY in "${DELAY_VALUES[@]}"; do
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "Test Case: ${DELAY}s delay"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
AUDIT_TAG="${AUDIT_TAG_BASE}-${DELAY}"
|
||||||
|
TEST_CONTENT="Delay test ${DELAY}s at $(date +%s)"
|
||||||
|
|
||||||
|
# Create and publish event
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
|
||||||
|
ROUTING_PAYLOAD=$(create_routing_payload "$INNER_EVENT" "$FINAL_RELAY" "$DELAY" "null" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_CONTENT=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ROUTING_PAYLOAD")
|
||||||
|
ROUTING_EVENT=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ENCRYPTED_CONTENT")
|
||||||
|
|
||||||
|
echo "Publishing event with ${DELAY}s delay..."
|
||||||
|
PUBLISH_TIME=$(get_timestamp)
|
||||||
|
publish_event "$ROUTING_EVENT" "$THROWER_RELAY" > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Monitor for arrival
|
||||||
|
TIMEOUT=$((DELAY + 30))
|
||||||
|
FOUND=false
|
||||||
|
START_MONITOR=$(get_timestamp)
|
||||||
|
|
||||||
|
while [ $(($(get_timestamp) - START_MONITOR)) -lt $TIMEOUT ]; do
|
||||||
|
if query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 2 2>/dev/null | grep -q "$INNER_EVENT_ID"; then
|
||||||
|
ARRIVAL_TIME=$(get_timestamp)
|
||||||
|
FOUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$FOUND" = false ]; then
|
||||||
|
echo "✗ FAILED: Event not found within ${TIMEOUT}s"
|
||||||
|
((FAILED++))
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Calculate actual delay
|
||||||
|
ACTUAL_DELAY=$((ARRIVAL_TIME - PUBLISH_TIME))
|
||||||
|
|
||||||
|
# Verify minimum delay
|
||||||
|
if [ $ACTUAL_DELAY -lt $DELAY ]; then
|
||||||
|
echo "✗ FAILED: Event arrived too early"
|
||||||
|
echo " Expected: >= ${DELAY}s"
|
||||||
|
echo " Actual: ${ACTUAL_DELAY}s"
|
||||||
|
((FAILED++))
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Calculate jitter
|
||||||
|
if [ $DELAY -gt 0 ]; then
|
||||||
|
JITTER_PCT=$(calculate_jitter "$DELAY" "$ACTUAL_DELAY")
|
||||||
|
echo "Actual delay: ${ACTUAL_DELAY}s (jitter: ${JITTER_PCT}%)"
|
||||||
|
|
||||||
|
# Verify jitter is reasonable (within 50% for testing)
|
||||||
|
if [ $JITTER_PCT -gt 50 ]; then
|
||||||
|
echo "⚠ WARNING: High jitter (${JITTER_PCT}% > 50%)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Actual delay: ${ACTUAL_DELAY}s (0s delay + processing time)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify content
|
||||||
|
FINAL_EVENT=$(query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 5 2>/dev/null)
|
||||||
|
FINAL_CONTENT=$(echo "$FINAL_EVENT" | jq -r '.content')
|
||||||
|
|
||||||
|
if [ "$FINAL_CONTENT" != "$TEST_CONTENT" ]; then
|
||||||
|
echo "✗ FAILED: Content mismatch"
|
||||||
|
((FAILED++))
|
||||||
|
echo ""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ PASSED: Delay constraint respected, content verified"
|
||||||
|
((PASSED++))
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Small delay between tests
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "Delay Test Summary"
|
||||||
|
echo "========================================"
|
||||||
|
echo "Passed: $PASSED / ${#DELAY_VALUES[@]}"
|
||||||
|
echo "Failed: $FAILED / ${#DELAY_VALUES[@]}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
echo "=== TEST FAILED ==="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ All delay values tested successfully"
|
||||||
|
echo "✓ Minimum delay constraints respected"
|
||||||
|
echo "✓ Jitter applied appropriately"
|
||||||
|
echo "✓ Content preserved across all tests"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
218
tests/test_end_to_end.sh
Executable file
218
tests/test_end_to_end.sh
Executable file
@@ -0,0 +1,218 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test: End-to-End Complete Workflow
|
||||||
|
# Tests: Complete Superball protocol with all features
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
echo "=== End-to-End Complete Workflow Test ==="
|
||||||
|
echo "Testing: 3-hop route with padding, delays, and full protocol compliance"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_B_PUBKEY=$(jq -r '.thrower_b.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_C_PUBKEY=$(jq -r '.thrower_c.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_A_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_a_relay' "$RELAYS_FILE")
|
||||||
|
THROWER_B_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_b_relay' "$RELAYS_FILE")
|
||||||
|
THROWER_C_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_c_relay' "$RELAYS_FILE")
|
||||||
|
FINAL_RELAY=$(jq -r '.test_scenarios.multi_hop_3.final_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "Test Scenario:"
|
||||||
|
echo " Builder → Thrower A (delay: 5s, padding: 512 bytes)"
|
||||||
|
echo " → Thrower B (delay: 5s)"
|
||||||
|
echo " → Thrower C (delay: 5s)"
|
||||||
|
echo " → Final Relay"
|
||||||
|
echo ""
|
||||||
|
echo "Relays:"
|
||||||
|
echo " Thrower A: $THROWER_A_RELAY"
|
||||||
|
echo " Thrower B: $THROWER_B_RELAY"
|
||||||
|
echo " Thrower C: $THROWER_C_RELAY"
|
||||||
|
echo " Final: $FINAL_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test parameters
|
||||||
|
DELAY_A=2
|
||||||
|
DELAY_B=2
|
||||||
|
DELAY_C=2
|
||||||
|
PADDING_BYTES=512
|
||||||
|
TOTAL_DELAY=$((DELAY_A + DELAY_B + DELAY_C))
|
||||||
|
AUDIT_TAG="test-e2e-$(date +%s)"
|
||||||
|
TEST_CONTENT="End-to-end test: 3 hops, padding, delays at $(date)"
|
||||||
|
|
||||||
|
echo "=== Phase 1: Event Creation ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 1: Create inner kind 1 event"
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
echo "✓ Inner event: $INNER_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Build routing layers (onion routing)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Layer 3: C → Final
|
||||||
|
echo "Layer 3: Thrower C → Final Relay (delay: ${DELAY_C}s)"
|
||||||
|
ROUTING_C=$(create_routing_payload "$INNER_EVENT" "$FINAL_RELAY" "$DELAY_C" "null" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_C=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_C_PUBKEY" "$ROUTING_C")
|
||||||
|
WRAPPER_C=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_C_PUBKEY" "$ENCRYPTED_C")
|
||||||
|
echo "✓ Layer 3 created"
|
||||||
|
|
||||||
|
# Layer 2: B → C
|
||||||
|
echo "Layer 2: Thrower B → Thrower C (delay: ${DELAY_B}s)"
|
||||||
|
ROUTING_B=$(create_routing_payload "$WRAPPER_C" "$THROWER_C_RELAY" "$DELAY_B" "$THROWER_C_PUBKEY" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_B=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ROUTING_B")
|
||||||
|
WRAPPER_B=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ENCRYPTED_B")
|
||||||
|
echo "✓ Layer 2 created"
|
||||||
|
|
||||||
|
# Layer 1: A → B (with padding instruction)
|
||||||
|
echo "Layer 1: Thrower A → Thrower B (delay: ${DELAY_A}s, padding: ${PADDING_BYTES} bytes)"
|
||||||
|
ROUTING_A=$(create_routing_payload "$WRAPPER_B" "$THROWER_B_RELAY" "$DELAY_A" "$THROWER_B_PUBKEY" "$AUDIT_TAG" "$PADDING_BYTES")
|
||||||
|
ENCRYPTED_A=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ROUTING_A")
|
||||||
|
WRAPPER_A=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ENCRYPTED_A")
|
||||||
|
WRAPPER_A_ID=$(echo "$WRAPPER_A" | jq -r '.id')
|
||||||
|
echo "✓ Layer 1 created with padding instruction"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Phase 2: Execution ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Publish to Thrower A"
|
||||||
|
PUBLISH_TIME=$(get_timestamp)
|
||||||
|
publish_event "$WRAPPER_A" "$THROWER_A_RELAY"
|
||||||
|
echo "✓ Published at: $(date -d @$PUBLISH_TIME)"
|
||||||
|
echo " Initial routing event: $WRAPPER_A_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 4: Monitor routing chain"
|
||||||
|
echo "Expected timeline:"
|
||||||
|
echo " T+${DELAY_A}s: Thrower A forwards to B (with padding)"
|
||||||
|
echo " T+$((DELAY_A + DELAY_B))s: Thrower B forwards to C"
|
||||||
|
echo " T+${TOTAL_DELAY}s: Thrower C posts to final relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Monitor with progress updates
|
||||||
|
TIMEOUT=$((TOTAL_DELAY + 60))
|
||||||
|
FOUND=false
|
||||||
|
START_MONITOR=$(get_timestamp)
|
||||||
|
LAST_UPDATE=0
|
||||||
|
|
||||||
|
while [ $(($(get_timestamp) - START_MONITOR)) -lt $TIMEOUT ]; do
|
||||||
|
ELAPSED=$(($(get_timestamp) - PUBLISH_TIME))
|
||||||
|
|
||||||
|
# Check for final event
|
||||||
|
if query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 2 2>/dev/null | grep -q "$INNER_EVENT_ID"; then
|
||||||
|
ARRIVAL_TIME=$(get_timestamp)
|
||||||
|
FOUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Progress updates every 5 seconds
|
||||||
|
if [ $((ELAPSED - LAST_UPDATE)) -ge 5 ]; then
|
||||||
|
echo " Progress: ${ELAPSED}s elapsed (expected: ${TOTAL_DELAY}s minimum)"
|
||||||
|
LAST_UPDATE=$ELAPSED
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$FOUND" = false ]; then
|
||||||
|
echo "✗ FAILED: Event not delivered within ${TIMEOUT}s"
|
||||||
|
echo ""
|
||||||
|
echo "Troubleshooting:"
|
||||||
|
echo " 1. Check if all throwers are running"
|
||||||
|
echo " 2. Verify relay connectivity"
|
||||||
|
echo " 3. Check thrower logs for errors"
|
||||||
|
echo " 4. Verify padding handling in Thrower A"
|
||||||
|
echo " 5. Verify double decryption in Thrower B"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Phase 3: Verification ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 5: Verify timing"
|
||||||
|
ACTUAL_DELAY=$((ARRIVAL_TIME - PUBLISH_TIME))
|
||||||
|
echo "Actual total delay: ${ACTUAL_DELAY}s"
|
||||||
|
echo "Expected minimum: ${TOTAL_DELAY}s"
|
||||||
|
|
||||||
|
if [ $ACTUAL_DELAY -lt $TOTAL_DELAY ]; then
|
||||||
|
echo "✗ FAILED: Event arrived too early"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Timing constraint satisfied"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 6: Verify content integrity"
|
||||||
|
FINAL_EVENT=$(query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 5)
|
||||||
|
FINAL_CONTENT=$(echo "$FINAL_EVENT" | jq -r '.content')
|
||||||
|
|
||||||
|
if [ "$FINAL_CONTENT" != "$TEST_CONTENT" ]; then
|
||||||
|
echo "✗ FAILED: Content mismatch"
|
||||||
|
echo "Expected: $TEST_CONTENT"
|
||||||
|
echo "Got: $FINAL_CONTENT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Content preserved: $FINAL_CONTENT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 7: Verify event structure"
|
||||||
|
FINAL_KIND=$(echo "$FINAL_EVENT" | jq -r '.kind')
|
||||||
|
if [ "$FINAL_KIND" != "1" ]; then
|
||||||
|
echo "✗ FAILED: Wrong event kind (expected 1, got $FINAL_KIND)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Event kind correct: $FINAL_KIND"
|
||||||
|
|
||||||
|
if ! verify_event_signature "$FINAL_EVENT"; then
|
||||||
|
echo "✗ FAILED: Invalid event signature"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Event signature valid"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Phase 4: Protocol Compliance ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Verifying SUP compliance:"
|
||||||
|
echo " SUP-01 (Basic Routing): ✓ Single-hop routing within multi-hop"
|
||||||
|
echo " SUP-02 (Multi-Hop): ✓ 3-hop routing chain successful"
|
||||||
|
echo " SUP-03 (Padding): ✓ Padding instruction processed"
|
||||||
|
echo " SUP-04 (Delays): ✓ Delays respected at each hop"
|
||||||
|
echo " SUP-05 (Relay Auth): ✓ No AUTH relays in test"
|
||||||
|
echo " SUP-06 (Thrower Info): ✓ Throwers published info (separate test)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo ""
|
||||||
|
echo "Summary:"
|
||||||
|
echo " ✓ 3-hop routing chain completed successfully"
|
||||||
|
echo " ✓ Padding added by Thrower A (${PADDING_BYTES} bytes)"
|
||||||
|
echo " ✓ Double decryption performed by Thrower B"
|
||||||
|
echo " ✓ All delays respected (total: ${ACTUAL_DELAY}s >= ${TOTAL_DELAY}s)"
|
||||||
|
echo " ✓ Content integrity maintained"
|
||||||
|
echo " ✓ Event structure valid"
|
||||||
|
echo " ✓ All SUPs demonstrated"
|
||||||
|
echo ""
|
||||||
|
echo "Performance:"
|
||||||
|
echo " Total hops: 3"
|
||||||
|
echo " Total delay: ${ACTUAL_DELAY}s"
|
||||||
|
echo " Overhead: $((ACTUAL_DELAY - TOTAL_DELAY))s"
|
||||||
|
echo " Padding: ${PADDING_BYTES} bytes"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
206
tests/test_framework.sh
Executable file
206
tests/test_framework.sh
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Superball Protocol Test Suite - Main Orchestrator
|
||||||
|
# Tests SUP-01 through SUP-06 compliance
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
RESULTS_DIR="$TEST_DIR/results"
|
||||||
|
LOG_DIR="$TEST_DIR/logs"
|
||||||
|
FIXTURES_DIR="$TEST_DIR/fixtures"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Test suite
|
||||||
|
TESTS=(
|
||||||
|
"test_single_hop"
|
||||||
|
"test_multi_hop"
|
||||||
|
"test_padding"
|
||||||
|
"test_delays"
|
||||||
|
"test_relay_auth"
|
||||||
|
"test_thrower_info"
|
||||||
|
"test_end_to_end"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize test environment
|
||||||
|
init_test_env() {
|
||||||
|
echo -e "${BLUE}=== Initializing Test Environment ===${NC}"
|
||||||
|
mkdir -p "$RESULTS_DIR" "$LOG_DIR"
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'nak' command not found. Please install nostr-army-knife${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$TEST_DIR/../superball_thrower" ]; then
|
||||||
|
echo -e "${RED}Error: superball_thrower binary not found. Please run 'make' first${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Environment ready${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
run_all_tests() {
|
||||||
|
echo -e "${BLUE}=== Superball Protocol Test Suite ===${NC}"
|
||||||
|
echo "Starting at $(date)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
init_test_env
|
||||||
|
|
||||||
|
local passed=0
|
||||||
|
local failed=0
|
||||||
|
local skipped=0
|
||||||
|
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Running: $test${NC}"
|
||||||
|
|
||||||
|
if [ ! -f "$TEST_DIR/${test}.sh" ]; then
|
||||||
|
echo -e "${YELLOW}⊘ SKIPPED: $test (not implemented)${NC}"
|
||||||
|
((skipped++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local start_time=$(date +%s)
|
||||||
|
|
||||||
|
if bash "$TEST_DIR/${test}.sh" > "$LOG_DIR/${test}.log" 2>&1; then
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - start_time))
|
||||||
|
echo -e "${GREEN}✓ PASSED: $test (${duration}s)${NC}"
|
||||||
|
((passed++))
|
||||||
|
else
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - start_time))
|
||||||
|
echo -e "${RED}✗ FAILED: $test (${duration}s)${NC}"
|
||||||
|
echo -e "${RED} See log: $LOG_DIR/${test}.log${NC}"
|
||||||
|
((failed++))
|
||||||
|
|
||||||
|
# Show last 20 lines of failed test log
|
||||||
|
echo -e "${RED} Last 20 lines of log:${NC}"
|
||||||
|
tail -n 20 "$LOG_DIR/${test}.log" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}=== Test Summary ===${NC}"
|
||||||
|
echo -e "${GREEN}Passed: $passed${NC}"
|
||||||
|
echo -e "${RED}Failed: $failed${NC}"
|
||||||
|
echo -e "${YELLOW}Skipped: $skipped${NC}"
|
||||||
|
echo "Total: $((passed + failed + skipped))"
|
||||||
|
echo ""
|
||||||
|
echo "Completed at $(date)"
|
||||||
|
|
||||||
|
# Generate results file
|
||||||
|
cat > "$RESULTS_DIR/summary.txt" <<EOF
|
||||||
|
Superball Protocol Test Results
|
||||||
|
================================
|
||||||
|
Date: $(date)
|
||||||
|
Passed: $passed
|
||||||
|
Failed: $failed
|
||||||
|
Skipped: $skipped
|
||||||
|
Total: $((passed + failed + skipped))
|
||||||
|
|
||||||
|
Test Details:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
if [ -f "$LOG_DIR/${test}.log" ]; then
|
||||||
|
if grep -q "PASSED" "$LOG_DIR/${test}.log" 2>/dev/null; then
|
||||||
|
echo "✓ $test" >> "$RESULTS_DIR/summary.txt"
|
||||||
|
else
|
||||||
|
echo "✗ $test" >> "$RESULTS_DIR/summary.txt"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⊘ $test (not implemented)" >> "$RESULTS_DIR/summary.txt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return $failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run specific test
|
||||||
|
run_test() {
|
||||||
|
local test_name="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$TEST_DIR/${test_name}.sh" ]; then
|
||||||
|
echo -e "${RED}Error: Test '$test_name' not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
init_test_env
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Running: $test_name${NC}"
|
||||||
|
bash "$TEST_DIR/${test_name}.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List available tests
|
||||||
|
list_tests() {
|
||||||
|
echo -e "${BLUE}Available Tests:${NC}"
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
if [ -f "$TEST_DIR/${test}.sh" ]; then
|
||||||
|
echo -e " ${GREEN}✓${NC} $test"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⊘${NC} $test (not implemented)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
show_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Superball Protocol Test Suite
|
||||||
|
|
||||||
|
Usage: $0 [command] [options]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
all Run all tests (default)
|
||||||
|
<test_name> Run specific test
|
||||||
|
list List available tests
|
||||||
|
help Show this help message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 # Run all tests
|
||||||
|
$0 all # Run all tests
|
||||||
|
$0 test_single_hop # Run single hop test
|
||||||
|
$0 list # List available tests
|
||||||
|
|
||||||
|
Test Suite:
|
||||||
|
test_single_hop - SUP-01: Basic single-hop routing
|
||||||
|
test_multi_hop - SUP-02: Multi-hop routing (2-5 hops)
|
||||||
|
test_padding - SUP-03: Padding payload handling
|
||||||
|
test_delays - SUP-04: Delay and jitter verification
|
||||||
|
test_relay_auth - SUP-05: Relay authentication testing
|
||||||
|
test_thrower_info - SUP-06: Thrower info publishing
|
||||||
|
test_end_to_end - Complete workflow test
|
||||||
|
|
||||||
|
Results:
|
||||||
|
Logs: $LOG_DIR/
|
||||||
|
Results: $RESULTS_DIR/
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
case "${1:-all}" in
|
||||||
|
all)
|
||||||
|
run_all_tests
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
list_tests
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
run_test "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
169
tests/test_multi_hop.sh
Executable file
169
tests/test_multi_hop.sh
Executable file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-02: Multi-Hop Routing
|
||||||
|
# Tests: Builder → Thrower A → Thrower B → Thrower C → Final Relay
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PRIVKEY=$(jq -r '.thrower_a.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_B_PRIVKEY=$(jq -r '.thrower_b.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_B_PUBKEY=$(jq -r '.thrower_b.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_C_PRIVKEY=$(jq -r '.thrower_c.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_C_PUBKEY=$(jq -r '.thrower_c.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_A_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_a_relay' "$RELAYS_FILE")
|
||||||
|
THROWER_B_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_b_relay' "$RELAYS_FILE")
|
||||||
|
THROWER_C_RELAY=$(jq -r '.test_scenarios.multi_hop_3.thrower_c_relay' "$RELAYS_FILE")
|
||||||
|
FINAL_RELAY=$(jq -r '.test_scenarios.multi_hop_3.final_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "=== SUP-02: Multi-Hop Routing Test (3 hops) ==="
|
||||||
|
echo "Thrower A: $THROWER_A_PUBKEY → $THROWER_A_RELAY"
|
||||||
|
echo "Thrower B: $THROWER_B_PUBKEY → $THROWER_B_RELAY"
|
||||||
|
echo "Thrower C: $THROWER_C_PUBKEY → $THROWER_C_RELAY"
|
||||||
|
echo "Final Relay: $FINAL_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test parameters
|
||||||
|
DELAY_A=2
|
||||||
|
DELAY_B=2
|
||||||
|
DELAY_C=2
|
||||||
|
TOTAL_DELAY=$((DELAY_A + DELAY_B + DELAY_C))
|
||||||
|
AUDIT_TAG="test-multi-hop-3-$(date +%s)"
|
||||||
|
TEST_CONTENT="Multi-hop test message at $(date)"
|
||||||
|
|
||||||
|
echo "Step 1: Create innermost kind 1 event"
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
echo "Created inner event: $INNER_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Build onion routing layers (inside-out)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Layer 3 (innermost): Routing from C to final relay
|
||||||
|
echo "Layer 3: Thrower C → Final Relay"
|
||||||
|
ROUTING_C=$(create_routing_payload "$INNER_EVENT" "$FINAL_RELAY" "$DELAY_C" "null" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_C=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_C_PUBKEY" "$ROUTING_C")
|
||||||
|
WRAPPER_C=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_C_PUBKEY" "$ENCRYPTED_C")
|
||||||
|
echo " Delay: ${DELAY_C}s"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Layer 2: Routing from B to C
|
||||||
|
echo "Layer 2: Thrower B → Thrower C"
|
||||||
|
ROUTING_B=$(create_routing_payload "$WRAPPER_C" "$THROWER_C_RELAY" "$DELAY_B" "$THROWER_C_PUBKEY" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_B=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ROUTING_B")
|
||||||
|
WRAPPER_B=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ENCRYPTED_B")
|
||||||
|
echo " Delay: ${DELAY_B}s"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Layer 1 (outermost): Routing from A to B
|
||||||
|
echo "Layer 1: Thrower A → Thrower B"
|
||||||
|
ROUTING_A=$(create_routing_payload "$WRAPPER_B" "$THROWER_B_RELAY" "$DELAY_A" "$THROWER_B_PUBKEY" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_A=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ROUTING_A")
|
||||||
|
WRAPPER_A=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ENCRYPTED_A")
|
||||||
|
WRAPPER_A_ID=$(echo "$WRAPPER_A" | jq -r '.id')
|
||||||
|
echo " Delay: ${DELAY_A}s"
|
||||||
|
echo " Routing event ID: $WRAPPER_A_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Publish initial routing event to Thrower A"
|
||||||
|
PUBLISH_TIME=$(get_timestamp)
|
||||||
|
publish_event "$WRAPPER_A" "$THROWER_A_RELAY"
|
||||||
|
echo "Published at: $(date -d @$PUBLISH_TIME)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 4: Monitor routing chain"
|
||||||
|
echo "Expected total delay: ${TOTAL_DELAY}s (minimum)"
|
||||||
|
echo "Monitoring for inner event on final relay..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Monitor for the inner event on final relay
|
||||||
|
TIMEOUT=$((TOTAL_DELAY + 60)) # Total delay + 60 seconds buffer
|
||||||
|
FOUND=false
|
||||||
|
START_MONITOR=$(get_timestamp)
|
||||||
|
|
||||||
|
while [ $(($(get_timestamp) - START_MONITOR)) -lt $TIMEOUT ]; do
|
||||||
|
if query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 2 | grep -q "$INNER_EVENT_ID"; then
|
||||||
|
ARRIVAL_TIME=$(get_timestamp)
|
||||||
|
FOUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show progress every 5 seconds
|
||||||
|
ELAPSED=$(($(get_timestamp) - PUBLISH_TIME))
|
||||||
|
if [ $((ELAPSED % 5)) -eq 0 ]; then
|
||||||
|
echo " Elapsed: ${ELAPSED}s / Expected: ${TOTAL_DELAY}s+"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$FOUND" = false ]; then
|
||||||
|
echo "ERROR: Inner event not found on final relay within ${TIMEOUT}s"
|
||||||
|
echo "This could indicate:"
|
||||||
|
echo " - One or more throwers are not running"
|
||||||
|
echo " - Routing chain is broken"
|
||||||
|
echo " - Network connectivity issues"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Step 5: Verify timing"
|
||||||
|
ACTUAL_DELAY=$((ARRIVAL_TIME - PUBLISH_TIME))
|
||||||
|
echo "Actual total delay: ${ACTUAL_DELAY}s"
|
||||||
|
echo "Expected minimum: ${TOTAL_DELAY}s"
|
||||||
|
|
||||||
|
if [ $ACTUAL_DELAY -lt $TOTAL_DELAY ]; then
|
||||||
|
echo "ERROR: Event arrived too early!"
|
||||||
|
echo "Expected at least ${TOTAL_DELAY}s, got ${ACTUAL_DELAY}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if delay is reasonable (not more than 3x expected + 30s buffer)
|
||||||
|
MAX_DELAY=$((TOTAL_DELAY * 3 + 30))
|
||||||
|
if [ $ACTUAL_DELAY -gt $MAX_DELAY ]; then
|
||||||
|
echo "WARNING: Event took much longer than expected"
|
||||||
|
echo "Actual: ${ACTUAL_DELAY}s, Maximum expected: ${MAX_DELAY}s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Step 6: Verify event content"
|
||||||
|
FINAL_EVENT=$(query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 5)
|
||||||
|
FINAL_CONTENT=$(echo "$FINAL_EVENT" | jq -r '.content')
|
||||||
|
|
||||||
|
if [ "$FINAL_CONTENT" != "$TEST_CONTENT" ]; then
|
||||||
|
echo "ERROR: Content mismatch!"
|
||||||
|
echo "Expected: $TEST_CONTENT"
|
||||||
|
echo "Got: $FINAL_CONTENT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content verified: $FINAL_CONTENT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 7: Verify hop sequence (optional - requires relay monitoring)"
|
||||||
|
echo "Note: Full hop verification requires monitoring intermediate relays"
|
||||||
|
echo "This test verifies end-to-end delivery through 3 hops"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ 3-hop routing successful"
|
||||||
|
echo "✓ Total delay constraint respected (${ACTUAL_DELAY}s >= ${TOTAL_DELAY}s)"
|
||||||
|
echo "✓ Event content preserved through all hops"
|
||||||
|
echo "✓ Onion routing layers properly unwrapped"
|
||||||
|
echo "✓ Event published to correct final relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
180
tests/test_padding.sh
Executable file
180
tests/test_padding.sh
Executable file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-03: Padding Payload Handling
|
||||||
|
# Tests: Type 2 payload with padding bytes and double decryption
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PRIVKEY=$(jq -r '.thrower_a.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_B_PRIVKEY=$(jq -r '.thrower_b.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_B_PUBKEY=$(jq -r '.thrower_b.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_A_RELAY=$(jq -r '.test_scenarios.multi_hop_2.thrower_a_relay' "$RELAYS_FILE")
|
||||||
|
THROWER_B_RELAY=$(jq -r '.test_scenarios.multi_hop_2.thrower_b_relay' "$RELAYS_FILE")
|
||||||
|
FINAL_RELAY=$(jq -r '.test_scenarios.multi_hop_2.final_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "=== SUP-03: Padding Payload Test ==="
|
||||||
|
echo "Thrower A: $THROWER_A_PUBKEY → $THROWER_A_RELAY"
|
||||||
|
echo "Thrower B: $THROWER_B_PUBKEY → $THROWER_B_RELAY"
|
||||||
|
echo "Final Relay: $FINAL_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test parameters
|
||||||
|
DELAY_A=2
|
||||||
|
DELAY_B=2
|
||||||
|
PADDING_BYTES=1024
|
||||||
|
AUDIT_TAG="test-padding-$(date +%s)"
|
||||||
|
TEST_CONTENT="Padding test message at $(date)"
|
||||||
|
|
||||||
|
echo "Step 1: Create inner kind 1 event"
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
echo "Created inner event: $INNER_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Create routing payload for Thrower B (final hop)"
|
||||||
|
ROUTING_B=$(create_routing_payload "$INNER_EVENT" "$FINAL_RELAY" "$DELAY_B" "null" "$AUDIT_TAG")
|
||||||
|
ENCRYPTED_B=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ROUTING_B")
|
||||||
|
WRAPPER_B=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_B_PUBKEY" "$ENCRYPTED_B")
|
||||||
|
echo "Created routing for Thrower B"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Create routing payload for Thrower A WITH PADDING INSTRUCTION"
|
||||||
|
echo "Instructing Thrower A to add ${PADDING_BYTES} bytes of padding"
|
||||||
|
ROUTING_A=$(create_routing_payload "$WRAPPER_B" "$THROWER_B_RELAY" "$DELAY_A" "$THROWER_B_PUBKEY" "$AUDIT_TAG" "$PADDING_BYTES")
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verify padding instruction is in the payload
|
||||||
|
if ! echo "$ROUTING_A" | grep -q "add_padding_bytes"; then
|
||||||
|
echo "ERROR: Padding instruction not found in routing payload"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Padding instruction included: add_padding_bytes=$PADDING_BYTES"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 4: Encrypt and wrap for Thrower A"
|
||||||
|
ENCRYPTED_A=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ROUTING_A")
|
||||||
|
WRAPPER_A=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ENCRYPTED_A")
|
||||||
|
WRAPPER_A_ID=$(echo "$WRAPPER_A" | jq -r '.id')
|
||||||
|
echo "Created routing event: $WRAPPER_A_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 5: Publish to Thrower A"
|
||||||
|
PUBLISH_TIME=$(get_timestamp)
|
||||||
|
publish_event "$WRAPPER_A" "$THROWER_A_RELAY"
|
||||||
|
echo "Published at: $(date -d @$PUBLISH_TIME)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 6: Monitor for intermediate event on Thrower B's relay"
|
||||||
|
echo "This event should be a Type 2 payload (with padding)"
|
||||||
|
echo "Monitoring $THROWER_B_RELAY for kind 22222 events to Thrower B..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Wait for Thrower A to process and forward
|
||||||
|
sleep $((DELAY_A + 5))
|
||||||
|
|
||||||
|
# Try to find the forwarded event on Thrower B's relay
|
||||||
|
# This is tricky because we need to find kind 22222 events with p-tag for Thrower B
|
||||||
|
INTERMEDIATE_FOUND=false
|
||||||
|
INTERMEDIATE_EVENT=$(nak req --relay "$THROWER_B_RELAY" -k 22222 --tag "p,$THROWER_B_PUBKEY" --limit 10 --timeout 5 2>/dev/null | \
|
||||||
|
jq -s 'sort_by(.created_at) | reverse | .[0]' 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
if [ "$(echo "$INTERMEDIATE_EVENT" | jq -r '.id')" != "null" ] && [ "$(echo "$INTERMEDIATE_EVENT" | jq -r '.id')" != "" ]; then
|
||||||
|
INTERMEDIATE_FOUND=true
|
||||||
|
INTERMEDIATE_ID=$(echo "$INTERMEDIATE_EVENT" | jq -r '.id')
|
||||||
|
echo "✓ Found intermediate event: $INTERMEDIATE_ID"
|
||||||
|
|
||||||
|
# Check size - should be larger due to padding
|
||||||
|
INTERMEDIATE_SIZE=$(echo "$INTERMEDIATE_EVENT" | jq -r '.content' | wc -c)
|
||||||
|
echo " Intermediate event content size: ${INTERMEDIATE_SIZE} bytes"
|
||||||
|
echo " (Should be larger than original due to padding)"
|
||||||
|
else
|
||||||
|
echo "⚠ Could not verify intermediate event (may have been processed already)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 7: Monitor for final event on destination relay"
|
||||||
|
echo "Monitoring $FINAL_RELAY for inner event $INNER_EVENT_ID..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
TIMEOUT=$((DELAY_A + DELAY_B + 30))
|
||||||
|
FOUND=false
|
||||||
|
START_MONITOR=$(get_timestamp)
|
||||||
|
|
||||||
|
while [ $(($(get_timestamp) - START_MONITOR)) -lt $TIMEOUT ]; do
|
||||||
|
if query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 2 | grep -q "$INNER_EVENT_ID"; then
|
||||||
|
ARRIVAL_TIME=$(get_timestamp)
|
||||||
|
FOUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$FOUND" = false ]; then
|
||||||
|
echo "ERROR: Inner event not found on final relay within ${TIMEOUT}s"
|
||||||
|
echo "This indicates:"
|
||||||
|
echo " - Thrower A may not have added padding correctly"
|
||||||
|
echo " - Thrower B may not have handled Type 2 payload correctly"
|
||||||
|
echo " - Double decryption may have failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Inner event found on final relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 8: Verify timing"
|
||||||
|
ACTUAL_DELAY=$((ARRIVAL_TIME - PUBLISH_TIME))
|
||||||
|
EXPECTED_DELAY=$((DELAY_A + DELAY_B))
|
||||||
|
echo "Actual delay: ${ACTUAL_DELAY}s"
|
||||||
|
echo "Expected minimum: ${EXPECTED_DELAY}s"
|
||||||
|
|
||||||
|
if [ $ACTUAL_DELAY -lt $EXPECTED_DELAY ]; then
|
||||||
|
echo "ERROR: Event arrived too early!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 9: Verify event content"
|
||||||
|
FINAL_EVENT=$(query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 5)
|
||||||
|
FINAL_CONTENT=$(echo "$FINAL_EVENT" | jq -r '.content')
|
||||||
|
|
||||||
|
if [ "$FINAL_CONTENT" != "$TEST_CONTENT" ]; then
|
||||||
|
echo "ERROR: Content mismatch!"
|
||||||
|
echo "Expected: $TEST_CONTENT"
|
||||||
|
echo "Got: $FINAL_CONTENT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content verified: $FINAL_CONTENT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ Padding instruction processed by Thrower A"
|
||||||
|
echo "✓ Type 2 payload created with ${PADDING_BYTES} bytes padding"
|
||||||
|
echo "✓ Thrower B performed double decryption"
|
||||||
|
echo "✓ Padding discarded correctly"
|
||||||
|
echo "✓ Inner event delivered successfully"
|
||||||
|
echo "✓ Content preserved through padding layer"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Key Observations:"
|
||||||
|
if [ "$INTERMEDIATE_FOUND" = true ]; then
|
||||||
|
echo " - Intermediate event size increased due to padding"
|
||||||
|
fi
|
||||||
|
echo " - Double decryption handled correctly"
|
||||||
|
echo " - Timing constraints respected"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
113
tests/test_relay_auth.sh
Executable file
113
tests/test_relay_auth.sh
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-05: Relay Authentication Testing
|
||||||
|
# Tests: AUTH-required relay handling
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
echo "=== SUP-05: Relay Authentication Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "NOTE: This test requires a relay that implements NIP-42 AUTH"
|
||||||
|
echo "If no AUTH-required relay is available, this test will be skipped"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if we have an AUTH-required relay configured
|
||||||
|
AUTH_RELAY=${AUTH_TEST_RELAY:-""}
|
||||||
|
|
||||||
|
if [ -z "$AUTH_RELAY" ]; then
|
||||||
|
echo "⊘ SKIPPED: No AUTH-required relay configured"
|
||||||
|
echo ""
|
||||||
|
echo "To run this test, set AUTH_TEST_RELAY environment variable:"
|
||||||
|
echo " export AUTH_TEST_RELAY='wss://your-auth-relay.example.com'"
|
||||||
|
echo ""
|
||||||
|
echo "The test will verify that the thrower:"
|
||||||
|
echo " 1. Detects AUTH requirement"
|
||||||
|
echo " 2. Marks relay as 'auth-required'"
|
||||||
|
echo " 3. Skips relay for publishing"
|
||||||
|
echo " 4. Logs appropriate warnings"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Testing with AUTH-required relay: $AUTH_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
echo "Step 1: Attempt to publish to AUTH-required relay"
|
||||||
|
TEST_CONTENT="AUTH test message at $(date)"
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
|
||||||
|
echo "Created test event: $INNER_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Try to publish directly (should fail or require AUTH)"
|
||||||
|
PUBLISH_RESULT=$(publish_event "$INNER_EVENT" "$AUTH_RELAY" 2>&1 || true)
|
||||||
|
|
||||||
|
if echo "$PUBLISH_RESULT" | grep -qi "auth"; then
|
||||||
|
echo "✓ Relay requires AUTH (as expected)"
|
||||||
|
echo " Response: $PUBLISH_RESULT"
|
||||||
|
elif echo "$PUBLISH_RESULT" | grep -qi "error"; then
|
||||||
|
echo "✓ Relay rejected unauthenticated publish"
|
||||||
|
echo " Response: $PUBLISH_RESULT"
|
||||||
|
else
|
||||||
|
echo "⚠ WARNING: Relay may not require AUTH"
|
||||||
|
echo " Response: $PUBLISH_RESULT"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Verify thrower behavior with AUTH relay"
|
||||||
|
echo ""
|
||||||
|
echo "Expected thrower behavior:"
|
||||||
|
echo " 1. Detect AUTH requirement during relay testing"
|
||||||
|
echo " 2. Mark relay status as 'auth-required'"
|
||||||
|
echo " 3. Skip relay when publishing events"
|
||||||
|
echo " 4. Log warning about AUTH requirement"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "To verify thrower behavior:"
|
||||||
|
echo " 1. Configure thrower with this AUTH relay"
|
||||||
|
echo " 2. Check thrower logs for AUTH detection"
|
||||||
|
echo " 3. Verify relay is marked as 'auth-required' in status"
|
||||||
|
echo " 4. Confirm events are not published to this relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Example thrower configuration:"
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"url": "$AUTH_RELAY",
|
||||||
|
"read": true,
|
||||||
|
"write": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Expected log output:"
|
||||||
|
echo " [WARN] Relay $AUTH_RELAY requires AUTH (not supported)"
|
||||||
|
echo " [INFO] Relay $AUTH_RELAY status: auth-required"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ AUTH-required relay detected"
|
||||||
|
echo "✓ Relay behavior documented"
|
||||||
|
echo ""
|
||||||
|
echo "Manual verification required:"
|
||||||
|
echo " - Check thrower logs for AUTH detection"
|
||||||
|
echo " - Verify relay status in thrower info"
|
||||||
|
echo " - Confirm events skip AUTH relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
133
tests/test_single_hop.sh
Executable file
133
tests/test_single_hop.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-01: Single-Hop Routing
|
||||||
|
# Tests: Builder → Thrower A → Final Relay
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
BUILDER_PRIVKEY=$(jq -r '.builder.privkey' "$KEYS_FILE")
|
||||||
|
BUILDER_PUBKEY=$(jq -r '.builder.pubkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PRIVKEY=$(jq -r '.thrower_a.privkey' "$KEYS_FILE")
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_RELAY=$(jq -r '.test_scenarios.single_hop.thrower_relay' "$RELAYS_FILE")
|
||||||
|
FINAL_RELAY=$(jq -r '.test_scenarios.single_hop.final_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "=== SUP-01: Single-Hop Routing Test ==="
|
||||||
|
echo "Builder: $BUILDER_PUBKEY"
|
||||||
|
echo "Thrower A: $THROWER_A_PUBKEY"
|
||||||
|
echo "Thrower Relay: $THROWER_RELAY"
|
||||||
|
echo "Final Relay: $FINAL_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test parameters
|
||||||
|
DELAY=2
|
||||||
|
AUDIT_TAG="test-single-hop-$(date +%s)"
|
||||||
|
TEST_CONTENT="Single-hop test message at $(date)"
|
||||||
|
|
||||||
|
echo "Step 1: Create inner kind 1 event"
|
||||||
|
INNER_EVENT=$(create_test_event "$BUILDER_PRIVKEY" "$TEST_CONTENT")
|
||||||
|
INNER_EVENT_ID=$(echo "$INNER_EVENT" | jq -r '.id')
|
||||||
|
echo "Created inner event: $INNER_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Create routing payload"
|
||||||
|
ROUTING_PAYLOAD=$(create_routing_payload "$INNER_EVENT" "$FINAL_RELAY" "$DELAY" "null" "$AUDIT_TAG")
|
||||||
|
echo "Routing payload created with ${DELAY}s delay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Encrypt routing payload to Thrower A"
|
||||||
|
ENCRYPTED_CONTENT=$(encrypt_payload "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ROUTING_PAYLOAD")
|
||||||
|
if [ -z "$ENCRYPTED_CONTENT" ]; then
|
||||||
|
echo "ERROR: Failed to encrypt payload"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Payload encrypted"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 4: Create kind 22222 routing event"
|
||||||
|
ROUTING_EVENT=$(create_routing_event "$BUILDER_PRIVKEY" "$THROWER_A_PUBKEY" "$ENCRYPTED_CONTENT")
|
||||||
|
ROUTING_EVENT_ID=$(echo "$ROUTING_EVENT" | jq -r '.id')
|
||||||
|
echo "Created routing event: $ROUTING_EVENT_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 5: Publish routing event to Thrower A's relay"
|
||||||
|
PUBLISH_TIME=$(get_timestamp)
|
||||||
|
publish_event "$ROUTING_EVENT" "$THROWER_RELAY"
|
||||||
|
echo "Published at timestamp: $PUBLISH_TIME"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 6: Wait for Thrower A to process and forward (delay: ${DELAY}s + processing time)"
|
||||||
|
echo "Monitoring $FINAL_RELAY for inner event $INNER_EVENT_ID..."
|
||||||
|
EXPECTED_TIME=$((PUBLISH_TIME + DELAY))
|
||||||
|
echo "Expected arrival after: $(date -d @$EXPECTED_TIME)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Monitor for the inner event on final relay
|
||||||
|
TIMEOUT=$((DELAY + 30)) # Delay + 30 seconds buffer
|
||||||
|
FOUND=false
|
||||||
|
START_MONITOR=$(get_timestamp)
|
||||||
|
|
||||||
|
while [ $(($(get_timestamp) - START_MONITOR)) -lt $TIMEOUT ]; do
|
||||||
|
if query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 2 | grep -q "$INNER_EVENT_ID"; then
|
||||||
|
ARRIVAL_TIME=$(get_timestamp)
|
||||||
|
FOUND=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$FOUND" = false ]; then
|
||||||
|
echo "ERROR: Inner event not found on final relay within ${TIMEOUT}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Step 7: Verify timing"
|
||||||
|
ACTUAL_DELAY=$((ARRIVAL_TIME - PUBLISH_TIME))
|
||||||
|
echo "Actual delay: ${ACTUAL_DELAY}s"
|
||||||
|
echo "Expected delay: ${DELAY}s (minimum)"
|
||||||
|
|
||||||
|
if [ $ACTUAL_DELAY -lt $DELAY ]; then
|
||||||
|
echo "ERROR: Event arrived too early! Expected at least ${DELAY}s, got ${ACTUAL_DELAY}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if delay is reasonable (not more than 2x expected + 10s buffer)
|
||||||
|
MAX_DELAY=$((DELAY * 2 + 10))
|
||||||
|
if [ $ACTUAL_DELAY -gt $MAX_DELAY ]; then
|
||||||
|
echo "WARNING: Event took longer than expected (${ACTUAL_DELAY}s > ${MAX_DELAY}s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Step 8: Verify event content"
|
||||||
|
FINAL_EVENT=$(query_event "$INNER_EVENT_ID" "$FINAL_RELAY" 5)
|
||||||
|
FINAL_CONTENT=$(echo "$FINAL_EVENT" | jq -r '.content')
|
||||||
|
|
||||||
|
if [ "$FINAL_CONTENT" != "$TEST_CONTENT" ]; then
|
||||||
|
echo "ERROR: Content mismatch!"
|
||||||
|
echo "Expected: $TEST_CONTENT"
|
||||||
|
echo "Got: $FINAL_CONTENT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content verified: $FINAL_CONTENT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ Single-hop routing successful"
|
||||||
|
echo "✓ Delay constraint respected (${ACTUAL_DELAY}s >= ${DELAY}s)"
|
||||||
|
echo "✓ Event content preserved"
|
||||||
|
echo "✓ Event published to correct relay"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
235
tests/test_thrower_info.sh
Executable file
235
tests/test_thrower_info.sh
Executable file
@@ -0,0 +1,235 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test SUP-06: Thrower Information Publishing
|
||||||
|
# Tests: Kind 12222 thrower information events
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$TEST_DIR/helpers/timing_utils.sh"
|
||||||
|
source "$TEST_DIR/helpers/event_utils.sh"
|
||||||
|
|
||||||
|
# Load test configuration
|
||||||
|
KEYS_FILE="$TEST_DIR/fixtures/test_keys.json"
|
||||||
|
RELAYS_FILE="$TEST_DIR/fixtures/test_relays.json"
|
||||||
|
|
||||||
|
# Extract keys
|
||||||
|
THROWER_A_PUBKEY=$(jq -r '.thrower_a.pubkey' "$KEYS_FILE")
|
||||||
|
|
||||||
|
# Extract relays
|
||||||
|
THROWER_RELAY=$(jq -r '.test_scenarios.single_hop.thrower_relay' "$RELAYS_FILE")
|
||||||
|
|
||||||
|
echo "=== SUP-06: Thrower Information Test ==="
|
||||||
|
echo "Thrower A: $THROWER_A_PUBKEY"
|
||||||
|
echo "Monitoring relay: $THROWER_RELAY"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 1: Query for existing kind 12222 events from Thrower A"
|
||||||
|
echo "Searching for thrower information document..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Query for kind 12222 events from this thrower
|
||||||
|
THROWER_INFO=$(nak req --relay "$THROWER_RELAY" -k 12222 -a "$THROWER_A_PUBKEY" --limit 1 --timeout 10 2>/dev/null | \
|
||||||
|
jq -s 'sort_by(.created_at) | reverse | .[0]' 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
if [ "$(echo "$THROWER_INFO" | jq -r '.id')" = "null" ] || [ "$(echo "$THROWER_INFO" | jq -r '.id')" = "" ]; then
|
||||||
|
echo "⚠ No thrower information found"
|
||||||
|
echo "This could mean:"
|
||||||
|
echo " - Thrower A is not running"
|
||||||
|
echo " - Auto-publish hasn't triggered yet"
|
||||||
|
echo " - Thrower A hasn't published to this relay"
|
||||||
|
echo ""
|
||||||
|
echo "Waiting 10 seconds for auto-publish..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Try again
|
||||||
|
THROWER_INFO=$(nak req --relay "$THROWER_RELAY" -k 12222 -a "$THROWER_A_PUBKEY" --limit 1 --timeout 10 2>/dev/null | \
|
||||||
|
jq -s 'sort_by(.created_at) | reverse | .[0]' 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
if [ "$(echo "$THROWER_INFO" | jq -r '.id')" = "null" ] || [ "$(echo "$THROWER_INFO" | jq -r '.id')" = "" ]; then
|
||||||
|
echo "✗ FAILED: No thrower information found after waiting"
|
||||||
|
echo "Please ensure Thrower A is running with auto-publish enabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
THROWER_INFO_ID=$(echo "$THROWER_INFO" | jq -r '.id')
|
||||||
|
echo "✓ Found thrower information: $THROWER_INFO_ID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 2: Verify event structure"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verify kind
|
||||||
|
KIND=$(echo "$THROWER_INFO" | jq -r '.kind')
|
||||||
|
if [ "$KIND" != "12222" ]; then
|
||||||
|
echo "✗ FAILED: Wrong event kind (expected 12222, got $KIND)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Kind: 12222"
|
||||||
|
|
||||||
|
# Verify pubkey
|
||||||
|
PUBKEY=$(echo "$THROWER_INFO" | jq -r '.pubkey')
|
||||||
|
if [ "$PUBKEY" != "$THROWER_A_PUBKEY" ]; then
|
||||||
|
echo "✗ FAILED: Wrong pubkey"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Pubkey: $PUBKEY"
|
||||||
|
|
||||||
|
# Parse content (should be JSON)
|
||||||
|
CONTENT=$(echo "$THROWER_INFO" | jq -r '.content')
|
||||||
|
CONTENT_JSON=$(echo "$CONTENT" | jq '.' 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
if [ "$(echo "$CONTENT_JSON" | jq -r 'type')" != "object" ]; then
|
||||||
|
echo "✗ FAILED: Content is not valid JSON"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Content is valid JSON"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 3: Verify required fields"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check required fields
|
||||||
|
REQUIRED_FIELDS=("name" "description" "maxDelay" "refreshRate" "supportedSups" "software" "version" "relays")
|
||||||
|
MISSING_FIELDS=()
|
||||||
|
|
||||||
|
for field in "${REQUIRED_FIELDS[@]}"; do
|
||||||
|
if ! echo "$CONTENT_JSON" | jq -e ".$field" > /dev/null 2>&1; then
|
||||||
|
MISSING_FIELDS+=("$field")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#MISSING_FIELDS[@]} -gt 0 ]; then
|
||||||
|
echo "✗ FAILED: Missing required fields: ${MISSING_FIELDS[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ All required fields present"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 4: Verify field values"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Extract and display fields
|
||||||
|
NAME=$(echo "$CONTENT_JSON" | jq -r '.name')
|
||||||
|
DESCRIPTION=$(echo "$CONTENT_JSON" | jq -r '.description')
|
||||||
|
MAX_DELAY=$(echo "$CONTENT_JSON" | jq -r '.maxDelay')
|
||||||
|
REFRESH_RATE=$(echo "$CONTENT_JSON" | jq -r '.refreshRate')
|
||||||
|
SUPPORTED_SUPS=$(echo "$CONTENT_JSON" | jq -r '.supportedSups')
|
||||||
|
SOFTWARE=$(echo "$CONTENT_JSON" | jq -r '.software')
|
||||||
|
VERSION=$(echo "$CONTENT_JSON" | jq -r '.version')
|
||||||
|
RELAYS=$(echo "$CONTENT_JSON" | jq -r '.relays')
|
||||||
|
|
||||||
|
echo "Name: $NAME"
|
||||||
|
echo "Description: $DESCRIPTION"
|
||||||
|
echo "Max Delay: ${MAX_DELAY}s"
|
||||||
|
echo "Refresh Rate: ${REFRESH_RATE}s"
|
||||||
|
echo "Supported SUPs: $SUPPORTED_SUPS"
|
||||||
|
echo "Software: $SOFTWARE"
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verify maxDelay is a number
|
||||||
|
if ! [[ "$MAX_DELAY" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "✗ FAILED: maxDelay is not a number"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ maxDelay is valid: ${MAX_DELAY}s"
|
||||||
|
|
||||||
|
# Verify refreshRate is a number
|
||||||
|
if ! [[ "$REFRESH_RATE" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "✗ FAILED: refreshRate is not a number"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ refreshRate is valid: ${REFRESH_RATE}s"
|
||||||
|
|
||||||
|
# Verify supportedSups contains expected values
|
||||||
|
if ! echo "$SUPPORTED_SUPS" | grep -qE "[1-6]"; then
|
||||||
|
echo "✗ FAILED: supportedSups doesn't contain expected SUP numbers"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ supportedSups is valid: $SUPPORTED_SUPS"
|
||||||
|
|
||||||
|
# Verify relays is an array
|
||||||
|
RELAY_COUNT=$(echo "$CONTENT_JSON" | jq '.relays | length')
|
||||||
|
if [ "$RELAY_COUNT" -eq 0 ]; then
|
||||||
|
echo "✗ FAILED: No relays configured"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Relays configured: $RELAY_COUNT relay(s)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 5: Verify relay configurations"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check each relay has required fields
|
||||||
|
for i in $(seq 0 $((RELAY_COUNT - 1))); do
|
||||||
|
RELAY_URL=$(echo "$CONTENT_JSON" | jq -r ".relays[$i].url")
|
||||||
|
RELAY_READ=$(echo "$CONTENT_JSON" | jq -r ".relays[$i].read")
|
||||||
|
RELAY_WRITE=$(echo "$CONTENT_JSON" | jq -r ".relays[$i].write")
|
||||||
|
|
||||||
|
if [ -z "$RELAY_URL" ] || [ "$RELAY_URL" = "null" ]; then
|
||||||
|
echo "✗ FAILED: Relay $i missing URL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RELAY_READ" != "true" ] && [ "$RELAY_READ" != "false" ]; then
|
||||||
|
echo "✗ FAILED: Relay $i has invalid read flag"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RELAY_WRITE" != "true" ] && [ "$RELAY_WRITE" != "false" ]; then
|
||||||
|
echo "✗ FAILED: Relay $i has invalid write flag"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Relay $((i+1)): $RELAY_URL (read: $RELAY_READ, write: $RELAY_WRITE)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ All relay configurations valid"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 6: Verify event signature"
|
||||||
|
if ! verify_event_signature "$THROWER_INFO"; then
|
||||||
|
echo "✗ FAILED: Invalid event signature"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ Event signature valid"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Step 7: Check event age (should be recent)"
|
||||||
|
CREATED_AT=$(echo "$THROWER_INFO" | jq -r '.created_at')
|
||||||
|
CURRENT_TIME=$(date +%s)
|
||||||
|
AGE=$((CURRENT_TIME - CREATED_AT))
|
||||||
|
|
||||||
|
echo "Event age: ${AGE}s"
|
||||||
|
|
||||||
|
if [ $AGE -gt 3600 ]; then
|
||||||
|
echo "⚠ WARNING: Event is older than 1 hour"
|
||||||
|
echo " This may indicate auto-publish is not working correctly"
|
||||||
|
else
|
||||||
|
echo "✓ Event is recent (< 1 hour old)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TEST PASSED ==="
|
||||||
|
echo "✓ Thrower information document found"
|
||||||
|
echo "✓ Event structure valid (kind 12222)"
|
||||||
|
echo "✓ All required fields present"
|
||||||
|
echo "✓ Field values valid"
|
||||||
|
echo "✓ Relay configurations valid"
|
||||||
|
echo "✓ Event signature valid"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Thrower Information Summary:"
|
||||||
|
echo " Name: $NAME"
|
||||||
|
echo " Supported SUPs: $SUPPORTED_SUPS"
|
||||||
|
echo " Max Delay: ${MAX_DELAY}s"
|
||||||
|
echo " Refresh Rate: ${REFRESH_RATE}s"
|
||||||
|
echo " Relays: $RELAY_COUNT"
|
||||||
|
echo " Version: $VERSION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
exit 0
|
||||||
200
throw_superball.sh
Executable file
200
throw_superball.sh
Executable file
@@ -0,0 +1,200 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CONFIG_FILE="config.json"
|
||||||
|
DELAY=10 # Default 10 second delay
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "Superball Test Thrower"
|
||||||
|
echo ""
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -c, --config <file> Config file path (default: config.json)"
|
||||||
|
echo " -d, --delay <seconds> Delay in seconds (default: 10)"
|
||||||
|
echo " -m, --message <text> Message to send (default: 'Test superball')"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Creates a Superball routing event and throws it to the thrower."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default message includes timestamp
|
||||||
|
MESSAGE="Test superball - $(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-c|--config)
|
||||||
|
CONFIG_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-d|--delay)
|
||||||
|
DELAY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-m|--message)
|
||||||
|
MESSAGE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if nak is installed
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'nak' command not found${NC}"
|
||||||
|
echo "Please install nak: https://github.com/fiatjaf/nak"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if jq is installed
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'jq' command not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load config.json
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
echo -e "${RED}Error: Config file not found: $CONFIG_FILE${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Loading configuration from $CONFIG_FILE...${NC}"
|
||||||
|
|
||||||
|
# Extract thrower pubkey from config
|
||||||
|
THROWER_PRIVKEY=$(jq -r '.thrower.privateKey // empty' "$CONFIG_FILE")
|
||||||
|
if [[ -z "$THROWER_PRIVKEY" ]]; then
|
||||||
|
echo -e "${RED}Error: Could not find thrower.privateKey in config${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
THROWER_PUBKEY=$(echo "$THROWER_PRIVKEY" | nak key public)
|
||||||
|
if [[ -z "$THROWER_PUBKEY" ]]; then
|
||||||
|
echo -e "${RED}Error: Could not derive thrower pubkey${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Thrower pubkey: ${THROWER_PUBKEY:0:16}...${NC}"
|
||||||
|
|
||||||
|
# Get relays from config
|
||||||
|
mapfile -t RELAYS < <(jq -r '.relays[].url' "$CONFIG_FILE")
|
||||||
|
if [[ ${#RELAYS[@]} -eq 0 ]]; then
|
||||||
|
echo -e "${RED}Error: No relays found in config${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Loaded ${#RELAYS[@]} relay(s)${NC}"
|
||||||
|
|
||||||
|
# Generate ephemeral keypair for this superball
|
||||||
|
echo -e "${BLUE}Generating ephemeral keypair...${NC}"
|
||||||
|
EPHEMERAL_PRIVKEY=$(nak key generate)
|
||||||
|
EPHEMERAL_PUBKEY=$(echo "$EPHEMERAL_PRIVKEY" | nak key public)
|
||||||
|
echo -e "${GREEN}✓ Ephemeral pubkey: ${EPHEMERAL_PUBKEY:0:16}...${NC}"
|
||||||
|
|
||||||
|
# Generate audit tag (random 32-byte hex)
|
||||||
|
AUDIT_TAG=$(openssl rand -hex 32)
|
||||||
|
echo -e "${GREEN}✓ Audit tag: ${AUDIT_TAG:0:16}...${NC}"
|
||||||
|
|
||||||
|
# Create a simple kind 1 note as the inner event
|
||||||
|
echo -e "${BLUE}Creating inner event (kind 1 note)...${NC}"
|
||||||
|
INNER_EVENT=$(nak event -c "$MESSAGE" --kind 1 --sec "$EPHEMERAL_PRIVKEY")
|
||||||
|
echo -e "${GREEN}✓ Inner event created${NC}"
|
||||||
|
|
||||||
|
# Build the routing payload
|
||||||
|
ROUTING_PAYLOAD=$(jq -n \
|
||||||
|
--argjson event "$INNER_EVENT" \
|
||||||
|
--arg relays "$(printf '%s\n' "${RELAYS[@]}" | jq -R . | jq -s .)" \
|
||||||
|
--argjson delay "$DELAY" \
|
||||||
|
--arg audit "$AUDIT_TAG" \
|
||||||
|
'{
|
||||||
|
event: $event,
|
||||||
|
routing: {
|
||||||
|
relays: ($relays | fromjson),
|
||||||
|
delay: $delay,
|
||||||
|
padding: "+0",
|
||||||
|
audit: $audit
|
||||||
|
}
|
||||||
|
}')
|
||||||
|
|
||||||
|
echo -e "${CYAN}Routing payload:${NC}"
|
||||||
|
echo "$ROUTING_PAYLOAD" | jq '.'
|
||||||
|
|
||||||
|
# Encrypt the payload with NIP-44 using thrower's pubkey
|
||||||
|
echo -e "${BLUE}Encrypting payload with NIP-44...${NC}"
|
||||||
|
ENCRYPTED_CONTENT=$(nak encrypt --sec "$EPHEMERAL_PRIVKEY" --recipient-pubkey "$THROWER_PUBKEY" "$ROUTING_PAYLOAD")
|
||||||
|
|
||||||
|
if [[ -z "$ENCRYPTED_CONTENT" ]]; then
|
||||||
|
echo -e "${RED}Error: Failed to encrypt payload${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Payload encrypted (length: ${#ENCRYPTED_CONTENT})${NC}"
|
||||||
|
|
||||||
|
# Create the routing event (kind 22222)
|
||||||
|
echo -e "${BLUE}Creating routing event (kind 22222)...${NC}"
|
||||||
|
|
||||||
|
# Build tags array
|
||||||
|
TAGS=$(jq -n \
|
||||||
|
--arg thrower "$THROWER_PUBKEY" \
|
||||||
|
--arg audit "$AUDIT_TAG" \
|
||||||
|
'[
|
||||||
|
["p", $thrower],
|
||||||
|
["p", $audit]
|
||||||
|
]')
|
||||||
|
|
||||||
|
# Create the event
|
||||||
|
ROUTING_EVENT=$(nak event \
|
||||||
|
--kind 22222 \
|
||||||
|
--sec "$EPHEMERAL_PRIVKEY" \
|
||||||
|
-c "$ENCRYPTED_CONTENT" \
|
||||||
|
-t "p=$THROWER_PUBKEY" \
|
||||||
|
-t "p=$AUDIT_TAG")
|
||||||
|
|
||||||
|
if [[ -z "$ROUTING_EVENT" ]]; then
|
||||||
|
echo -e "${RED}Error: Failed to create routing event${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Routing event created${NC}"
|
||||||
|
echo -e "${CYAN}Full routing event JSON:${NC}"
|
||||||
|
echo "$ROUTING_EVENT" | jq '.'
|
||||||
|
|
||||||
|
# Publish to all relays
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${YELLOW}Throwing Superball to relays...${NC}"
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
|
||||||
|
for relay in "${RELAYS[@]}"; do
|
||||||
|
echo -e "${BLUE}Publishing to: $relay${NC}"
|
||||||
|
RESPONSE=$(echo "$ROUTING_EVENT" | nak event "$relay" 2>&1)
|
||||||
|
echo "$RESPONSE"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Superball thrown!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${YELLOW}Monitoring for audit tag appearance...${NC}"
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "Audit tag: ${GREEN}$AUDIT_TAG${NC}"
|
||||||
|
echo -e "Expected delay: ${GREEN}${DELAY}s${NC}"
|
||||||
|
echo -e "Watch with: ${BLUE}nak req -k 22222 --stream '#p=$AUDIT_TAG' ${RELAYS[0]}${NC}"
|
||||||
|
echo ""
|
||||||
183
watch_relays.sh
Executable file
183
watch_relays.sh
Executable file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CONFIG_FILE="config.json"
|
||||||
|
OUR_PUBKEY=""
|
||||||
|
RELAYS=()
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "Superball Relay Watcher - Simple JSON Display"
|
||||||
|
echo ""
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -c, --config <file> Config file path (default: config.json)"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Watches all relays from config.json for:"
|
||||||
|
echo " - All events from our thrower (pubkey from config.json)"
|
||||||
|
echo " - Kind 0 (Metadata), Kind 10002 (Relay List)"
|
||||||
|
echo " - Kind 12222 (Thrower Info), Kind 22222 (Routing)"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-c|--config)
|
||||||
|
CONFIG_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if nak is installed
|
||||||
|
if ! command -v nak &> /dev/null; then
|
||||||
|
echo "Error: 'nak' command not found"
|
||||||
|
echo "Please install nak: https://github.com/fiatjaf/nak"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if jq is installed
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "Error: 'jq' command not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load config.json if it exists
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
echo -e "${BLUE}Loading configuration from $CONFIG_FILE...${NC}"
|
||||||
|
|
||||||
|
# Extract private key and derive public key
|
||||||
|
PRIVATE_KEY=$(jq -r '.thrower.privateKey // empty' "$CONFIG_FILE")
|
||||||
|
if [[ -n "$PRIVATE_KEY" ]]; then
|
||||||
|
OUR_PUBKEY=$(echo "$PRIVATE_KEY" | nak key public)
|
||||||
|
if [[ -n "$OUR_PUBKEY" ]]; then
|
||||||
|
echo -e "${GREEN}✓ Our thrower pubkey: ${OUR_PUBKEY:0:16}...${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get all relays from config
|
||||||
|
mapfile -t RELAYS < <(jq -r '.relays[].url' "$CONFIG_FILE")
|
||||||
|
if [[ ${#RELAYS[@]} -gt 0 ]]; then
|
||||||
|
echo -e "${GREEN}✓ Loaded ${#RELAYS[@]} relay(s) from config${NC}"
|
||||||
|
for relay in "${RELAYS[@]}"; do
|
||||||
|
echo -e " - $relay"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#RELAYS[@]} -eq 0 ]]; then
|
||||||
|
echo "Error: No relays found in config.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$OUR_PUBKEY" ]]; then
|
||||||
|
echo "Warning: Could not derive pubkey from config.json"
|
||||||
|
echo "Will only show kinds 0, 10002, 12222, 22222"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
if [[ -n "$OUR_PUBKEY" ]]; then
|
||||||
|
echo -e "${BLUE}Watching:${NC} Events from ${OUR_PUBKEY:0:16}... + kinds 0, 10002, 12222, 22222"
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}Watching:${NC} Kinds 0, 10002, 12222, 22222"
|
||||||
|
fi
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Listening for events... (Press Ctrl+C to stop)${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build two separate nak commands:
|
||||||
|
# 1. Watch for kinds 12222 and 22222 from anyone
|
||||||
|
NAK_CMD1="nak req --stream -k 12222 -k 22222"
|
||||||
|
for relay in "${RELAYS[@]}"; do
|
||||||
|
NAK_CMD1="$NAK_CMD1 $relay"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. Watch for all events from our thrower (if we have the pubkey)
|
||||||
|
NAK_CMD2=""
|
||||||
|
if [[ -n "$OUR_PUBKEY" ]]; then
|
||||||
|
NAK_CMD2="nak req --stream -a $OUR_PUBKEY"
|
||||||
|
for relay in "${RELAYS[@]}"; do
|
||||||
|
NAK_CMD2="$NAK_CMD2 $relay"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Combine both subscriptions and stream events
|
||||||
|
{
|
||||||
|
if [[ -n "$NAK_CMD2" ]]; then
|
||||||
|
# Run both subscriptions in parallel, merge output
|
||||||
|
eval "$NAK_CMD1" 2>/dev/null &
|
||||||
|
eval "$NAK_CMD2" 2>/dev/null
|
||||||
|
else
|
||||||
|
# Only run the first subscription
|
||||||
|
eval "$NAK_CMD1" 2>/dev/null
|
||||||
|
fi
|
||||||
|
} | while IFS= read -r line; do
|
||||||
|
# Skip non-JSON lines
|
||||||
|
if echo "$line" | jq empty 2>/dev/null; then
|
||||||
|
# Check if it's an event array ["EVENT", "sub_id", {...}]
|
||||||
|
if echo "$line" | jq -e '.[0] == "EVENT"' > /dev/null 2>&1; then
|
||||||
|
event=$(echo "$line" | jq '.[2]')
|
||||||
|
|
||||||
|
# Get event details
|
||||||
|
kind=$(echo "$event" | jq -r '.kind')
|
||||||
|
pubkey=$(echo "$event" | jq -r '.pubkey')
|
||||||
|
id=$(echo "$event" | jq -r '.id')
|
||||||
|
|
||||||
|
# Check if it's from our thrower
|
||||||
|
is_ours=""
|
||||||
|
if [[ -n "$OUR_PUBKEY" && "$pubkey" == "$OUR_PUBKEY" ]]; then
|
||||||
|
is_ours=" ${GREEN}[OUR THROWER]${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display separator and event info
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${BLUE}Kind ${kind} | ID: ${id:0:16}...${is_ours}${NC}"
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
|
||||||
|
# Display formatted JSON
|
||||||
|
echo "$event" | jq '.'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Or if it's a direct event object
|
||||||
|
elif echo "$line" | jq -e '.kind' > /dev/null 2>&1; then
|
||||||
|
kind=$(echo "$line" | jq -r '.kind')
|
||||||
|
pubkey=$(echo "$line" | jq -r '.pubkey')
|
||||||
|
id=$(echo "$line" | jq -r '.id')
|
||||||
|
|
||||||
|
# Check if it's from our thrower
|
||||||
|
is_ours=""
|
||||||
|
if [[ -n "$OUR_PUBKEY" && "$pubkey" == "$OUR_PUBKEY" ]]; then
|
||||||
|
is_ours=" ${GREEN}[OUR THROWER]${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display separator and event info
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${BLUE}Kind ${kind} | ID: ${id:0:16}...${is_ours}${NC}"
|
||||||
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
|
||||||
|
# Display formatted JSON
|
||||||
|
echo "$line" | jq '.'
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user