v0.0.1 - Up and running
This commit is contained in:
516
plans/superball_thrower_c_architecture.md
Normal file
516
plans/superball_thrower_c_architecture.md
Normal 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
|
||||
Reference in New Issue
Block a user