v0.0.1 - Up and running

This commit is contained in:
Your Name
2025-12-10 10:17:24 -04:00
parent d436a4927d
commit 60543cf7c4
11 changed files with 2552 additions and 2 deletions

View File

@@ -0,0 +1,516 @@
# 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
```c
// 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
```c
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
```c
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
```c
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
```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
```bash
# 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
```json
{
"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)
- [x] 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