Files
super_ball_thrower/plans/superball_thrower_c_architecture.md
2025-12-10 10:17:24 -04:00

16 KiB

Superball Thrower C Implementation - Simplified Architecture

Executive Summary

This document outlines a simplified single-file architecture for implementing a Superball Thrower daemon in C using the nostr_core_lib submodule. The implementation will follow the Superball protocol (SUP-01 through SUP-06) and provide equivalent functionality to the reference Node.js implementation.

Implementation Approach: All functionality in a single main.c file for simplicity and rapid development. Can be refactored into modules later if needed.

1. Project Overview

1.1 Goals

  • Implement a production-ready Superball Thrower daemon in C
  • Leverage nostr_core_lib for all NOSTR protocol operations
  • Maintain protocol compatibility with the Node.js reference implementation
  • Provide better performance and lower resource usage than Node.js version
  • Support all SUPs (Superball Upgrade Proposals) 1-6

1.2 Key Features

  • Event Monitoring: Subscribe to kind 22222 events across multiple relays
  • NIP-44 Decryption: Decrypt routing payloads using NIP-44
  • Dual Payload Handling: Support both routing and padding payload types
  • Event Queue: Delayed processing with configurable delays and jitter
  • Relay Management: Automatic authentication testing and capability detection
  • Thrower Info Publishing: SUP-06 compliant service announcements
  • Configuration: JSON-based configuration file support

1.3 Why Single File?

  • Simplicity: Easier to understand the complete flow
  • Faster Development: No need to manage multiple files and headers
  • Easier Debugging: All code in one place
  • Simpler Build: Single compilation unit
  • Can Refactor Later: If file grows beyond ~2000 lines, split into modules

2. Single-File Architecture

2.1 File Organization in main.c

main.c (~1500-2000 lines)
├── [1] Includes & Constants (50 lines)
│   ├── System headers
│   ├── nostr_core_lib headers
│   └── Configuration constants
│
├── [2] Data Structures (150 lines)
│   ├── superball_thrower_t (main context)
│   ├── superball_config_t (configuration)
│   ├── relay_config_t (relay settings)
│   ├── routing_payload_t (routing instructions)
│   ├── padding_payload_t (padding wrapper)
│   ├── queue_item_t (queued event)
│   └── event_queue_t (event queue)
│
├── [3] Forward Declarations (30 lines)
│   └── All static function prototypes
│
├── [4] Global Variables (20 lines)
│   ├── volatile sig_atomic_t running
│   └── Global thrower instance
│
├── [5] Utility Functions (100 lines)
│   ├── log_info(), log_error(), log_debug()
│   ├── get_timestamp()
│   ├── add_jitter()
│   └── hex_to_bytes(), bytes_to_hex()
│
├── [6] Configuration Functions (200 lines)
│   ├── config_load()
│   ├── config_parse_thrower()
│   ├── config_parse_relays()
│   ├── config_validate()
│   └── config_free()
│
├── [7] Crypto Functions (150 lines)
│   ├── decrypt_nip44()
│   ├── encrypt_nip44()
│   ├── generate_padding()
│   └── generate_ephemeral_keypair()
│
├── [8] Queue Functions (200 lines)
│   ├── queue_create()
│   ├── queue_add()
│   ├── queue_get_ready()
│   ├── queue_process_ready_items()
│   └── queue_destroy()
│
├── [9] Relay Functions (150 lines)
│   ├── relay_test_auth()
│   ├── relay_publish_list()
│   └── relay_init()
│
├── [10] Event Processing Functions (400 lines)
│   ├── on_routing_event() [callback]
│   ├── on_eose() [callback]
│   ├── decrypt_payload()
│   ├── parse_routing_payload()
│   ├── parse_padding_payload()
│   ├── validate_routing()
│   ├── forward_to_next_thrower()
│   ├── post_final_event()
│   └── publish_callback()
│
├── [11] Thrower Info Functions (150 lines)
│   ├── publish_thrower_info()
│   ├── auto_publish_thread()
│   └── stop_auto_publish()
│
└── [12] Main Functions (200 lines)
    ├── signal_handler()
    ├── thrower_create()
    ├── thrower_start()
    ├── thrower_stop()
    ├── thrower_destroy()
    └── main()

Total: ~1800 lines

2.2 Dependency Structure

main.c
└── Depends on:
    └── nostr_core_lib/ (git submodule)
        ├── nostr_core/nostr_core.h
        ├── cjson/cJSON.h
        └── libnostr_core.a (static library)

3. Core Data Structures

// Main daemon context
typedef struct {
    superball_config_t* config;
    nostr_relay_pool_t* pool;
    event_queue_t* queue;
    pthread_t auto_publish_thread;
    unsigned char private_key[32];
    unsigned char public_key[32];
    int running;
    int auto_publish_running;
} superball_thrower_t;

// Configuration structure
typedef struct {
    char* private_key_hex;
    char* name;
    char* description;
    int max_delay;
    int refresh_rate;
    char* supported_sups;
    char* software;
    char* version;
    relay_config_t* relays;
    int relay_count;
    int max_queue_size;
} superball_config_t;

// Relay configuration
typedef struct {
    char* url;
    int read;
    int write;
    char* auth_status;  // "no-auth", "auth-required", "error", "unknown"
} relay_config_t;

// Routing payload (Type 1 - from builder)
typedef struct {
    cJSON* event;           // Inner event (encrypted or final)
    char** relays;          // Target relay URLs
    int relay_count;
    int delay;              // Delay in seconds
    char* next_hop_pubkey;  // NULL for final posting
    char* audit_tag;        // Required audit tag
    char* payment;          // Optional eCash token
    int add_padding_bytes;  // Optional padding instruction
} routing_payload_t;

// Padding payload (Type 2 - from previous thrower)
typedef struct {
    cJSON* event;           // Still-encrypted inner event
    char* padding;          // Padding data to discard
} padding_payload_t;

// Queue item
typedef struct {
    char event_id[65];
    cJSON* wrapped_event;
    routing_payload_t* routing;
    time_t received_at;
    time_t process_at;
    char status[32];        // "queued", "processing", "completed", "failed"
} queue_item_t;

// Event queue
typedef struct {
    queue_item_t** items;
    int count;
    int capacity;
    pthread_mutex_t mutex;
} event_queue_t;

// Payload type enum
typedef enum {
    PAYLOAD_ERROR = 0,
    PAYLOAD_ROUTING = 1,    // Type 1: Routing instructions from builder
    PAYLOAD_PADDING = 2     // Type 2: Padding wrapper from previous thrower
} payload_type_t;

4. Key Implementation Patterns

4.1 Event Monitoring Pattern

static void start_monitoring(superball_thrower_t* thrower) {
    // Create filter for kind 22222 with our pubkey
    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(22222));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    
    cJSON* p_tags = cJSON_CreateArray();
    char pubkey_hex[65];
    nostr_bytes_to_hex(thrower->public_key, 32, pubkey_hex);
    cJSON_AddItemToArray(p_tags, cJSON_CreateString(pubkey_hex));
    cJSON_AddItemToObject(filter, "#p", p_tags);
    
    // Get relay URLs
    const char** relay_urls = malloc(thrower->config->relay_count * sizeof(char*));
    for (int i = 0; i < thrower->config->relay_count; i++) {
        relay_urls[i] = thrower->config->relays[i].url;
    }
    
    // Subscribe
    nostr_relay_pool_subscribe(
        thrower->pool,
        relay_urls,
        thrower->config->relay_count,
        filter,
        on_routing_event,  // Event callback
        on_eose,           // EOSE callback
        thrower,           // User data
        0,                 // Don't close on EOSE
        1,                 // Enable deduplication
        NOSTR_POOL_EOSE_FULL_SET,
        30,                // Relay timeout
        60                 // EOSE timeout
    );
    
    free(relay_urls);
    cJSON_Delete(filter);
}

4.2 Payload Decryption Pattern

static payload_type_t decrypt_payload(superball_thrower_t* thrower, 
                                     cJSON* event, 
                                     void** payload_out) {
    cJSON* content = cJSON_GetObjectItem(event, "content");
    cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
    
    if (!content || !pubkey) return PAYLOAD_ERROR;
    
    char decrypted[65536];  // 64KB buffer
    int result = nostr_nip44_decrypt(
        thrower->private_key,
        cJSON_GetStringValue(pubkey),
        cJSON_GetStringValue(content),
        decrypted,
        sizeof(decrypted)
    );
    
    if (result != NOSTR_SUCCESS) return PAYLOAD_ERROR;
    
    cJSON* payload = cJSON_Parse(decrypted);
    if (!payload) return PAYLOAD_ERROR;
    
    // Check payload type
    if (cJSON_HasObjectItem(payload, "padding")) {
        // Type 2: Padding payload - discard padding, decrypt inner event
        *payload_out = parse_padding_payload(payload);
        cJSON_Delete(payload);
        return PAYLOAD_PADDING;
    } else if (cJSON_HasObjectItem(payload, "routing")) {
        // Type 1: Routing payload - process routing instructions
        *payload_out = parse_routing_payload(payload);
        cJSON_Delete(payload);
        return PAYLOAD_ROUTING;
    }
    
    cJSON_Delete(payload);
    return PAYLOAD_ERROR;
}

4.3 Double Decryption Pattern

static void on_routing_event(cJSON* event, const char* relay_url, void* user_data) {
    superball_thrower_t* thrower = (superball_thrower_t*)user_data;
    
    log_info("Received routing event from %s", relay_url);
    
    // First decryption
    void* payload = NULL;
    payload_type_t type = decrypt_payload(thrower, event, &payload);
    
    if (type == PAYLOAD_PADDING) {
        // Type 2: Padding payload - perform second decryption
        padding_payload_t* padding_payload = (padding_payload_t*)payload;
        log_info("Detected padding payload, discarding padding and performing second decryption");
        
        // Second decryption to get routing instructions
        void* routing_payload = NULL;
        payload_type_t inner_type = decrypt_payload(thrower, padding_payload->event, &routing_payload);
        
        if (inner_type == PAYLOAD_ROUTING) {
            // Process the routing instructions
            routing_payload_t* routing = (routing_payload_t*)routing_payload;
            queue_add(thrower->queue, create_queue_item(padding_payload->event, routing));
        }
        
        free_padding_payload(padding_payload);
    } else if (type == PAYLOAD_ROUTING) {
        // Type 1: Routing payload - process directly
        log_info("Detected routing payload, processing directly");
        routing_payload_t* routing = (routing_payload_t*)payload;
        queue_add(thrower->queue, create_queue_item(event, routing));
    }
}

5. nostr_core_lib API Usage

Functionality Function Usage in main.c
Pool Management nostr_relay_pool_create() thrower_create()
nostr_relay_pool_add_relay() relay_init()
nostr_relay_pool_destroy() thrower_destroy()
Subscriptions nostr_relay_pool_subscribe() start_monitoring()
nostr_relay_pool_poll() main() event loop
Publishing nostr_relay_pool_publish_async() forward_to_next_thrower(), post_final_event()
NIP-44 nostr_nip44_encrypt() encrypt_nip44()
nostr_nip44_decrypt() decrypt_nip44()
Keys nostr_generate_keypair() generate_ephemeral_keypair()
Events nostr_create_and_sign_event() forward_to_next_thrower(), publish_thrower_info()
Utilities nostr_bytes_to_hex() Various functions
nostr_hex_to_bytes() config_load()

6. Simplified File Structure

super_ball_thrower/
├── main.c                       # Complete implementation (~1800 lines)
├── config.example.json          # Example configuration
├── config.json                  # User configuration (gitignored)
├── Makefile                     # Build system
├── README.md                    # Documentation
├── .gitignore                   # Git ignore rules
├── nostr_core_lib/              # Git submodule
└── plans/
    └── superball_thrower_c_architecture.md

7. Build System

7.1 Makefile

CC = gcc
CFLAGS = -Wall -Wextra -O2 -I./nostr_core_lib/nostr_core -I./nostr_core_lib/cjson
LDFLAGS = -L./nostr_core_lib -lnostr_core -lssl -lcrypto -lcurl -lsecp256k1 -lm -lpthread

TARGET = superball_thrower
SOURCE = main.c

all: nostr_core_lib $(TARGET)

nostr_core_lib:
	@echo "Building nostr_core_lib..."
	cd nostr_core_lib && ./build.sh lib

$(TARGET): $(SOURCE)
	$(CC) $(CFLAGS) $(SOURCE) -o $(TARGET) $(LDFLAGS)

clean:
	rm -f $(TARGET)

distclean: clean
	cd nostr_core_lib && make clean

install: $(TARGET)
	install -m 755 $(TARGET) /usr/local/bin/

.PHONY: all clean distclean install nostr_core_lib

7.2 Build Commands

# Build everything
make

# Clean build artifacts
make clean

# Clean everything including nostr_core_lib
make distclean

# Install to system
sudo make install

8. Configuration File Format

{
  "thrower": {
    "privateKey": "hex_private_key_here",
    "name": "My C Superball Thrower",
    "description": "High-performance C implementation",
    "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
    },
    {
      "url": "wss://relay.damus.io",
      "read": true,
      "write": false
    }
  ],
  "daemon": {
    "logLevel": "info",
    "maxQueueSize": 1000
  }
}

9. Implementation Phases

Phase 1: Core Implementation (Days 1-3)

  • Architecture design
  • Create main.c with all sections:
    • Includes, constants, data structures
    • Configuration loading and parsing
    • Crypto wrapper functions
    • Event queue implementation
    • Event processing logic
    • Relay management
    • Thrower info publishing
    • Main function and event loop
  • Create Makefile
  • Create config.example.json

Phase 2: Testing & Refinement (Days 4-5)

  • Basic functionality testing
  • Integration test with Node.js thrower
  • Bug fixes and optimization
  • Documentation
  • Usage examples

Phase 3: Optional Enhancements (Future)

  • Split into modules if file becomes too large (>2000 lines)
  • Add systemd service file
  • Add installation script
  • Performance profiling
  • Additional logging options

10. Success Criteria

  1. Protocol Compliance: Pass all SUP-01 through SUP-06 requirements
  2. Interoperability: Successfully route events with Node.js throwers
  3. Performance: Handle 100+ events/second with <100MB RAM
  4. Reliability: 99.9% uptime in 24-hour test
  5. Code Quality: Clean, well-commented, single-file implementation

11. Advantages of Single-File Approach

  1. Simplicity: Everything in one place, easy to understand
  2. Fast Compilation: Single compilation unit
  3. Easy Debugging: No jumping between files
  4. Portable: Just copy main.c and build
  5. No Header Management: No .h files to maintain
  6. Static Functions: All implementation details are private
  7. Can Refactor Later: Easy to split if needed

12. When to Refactor into Modules

Consider splitting into modules if:

  • File exceeds 2000 lines
  • Multiple developers working on different features
  • Need to reuse components in other projects
  • Testing individual components separately becomes important

13. Next Steps

  1. Architecture design complete
  2. Switch to Code mode
  3. Implement main.c with all functionality
  4. Create Makefile and config.example.json
  5. Test with Node.js reference implementation
  6. Document usage and deployment

Document Version: 2.0 (Simplified)
Last Updated: 2025-12-10
Author: Roo (Architect Mode)
Approach: Single-file monolithic design for rapid development