Compare commits

...
93 Commits
Author SHA1 Message Date
Laan Tungir 8453d82ee4 v2.1.31 - Fix make_and_restart_relay.sh: load test keys by default, add --no-test-keys flag 2026-08-02 17:12:52 -04:00
Laan Tungir 8c6770c7e2 v2.1.30 - Add Event Cleanup admin page with saved queries, preview, and delete execution 2026-08-02 17:08:03 -04:00
Laan Tungir 7cd33aa6ea v2.1.29 - Remove SQLite support, hardcode PostgreSQL backend, fix admin API to use first_seen, add pgweb systemd service 2026-08-02 08:20:11 -04:00
Laan Tungir 81c5339063 v2.1.28 - Add kind-3 fallback for relay discovery (NIP-65 pre-standard relay lists in contact list r tags) 2026-07-31 13:58:24 -04:00
Laan Tungir 5ec5ed9bd4 v2.1.27 - Caching page: active backfill row highlight (red border), increase follows limit to 1000, accept npub or hex for caching_root_npubs, fix static caching binary build with PostgreSQL 2026-07-31 11:42:27 -04:00
Laan Tungir bd5150cd09 v2.1.26 - Accept both npub and hex format for caching_root_npubs, fix static caching binary build with PostgreSQL support 2026-07-31 11:23:09 -04:00
Laan Tungir 2e1d731de4 v2.1.25 - Fix: insert missing config keys on existing-relay startup, preserve config.php in deploy_lt.sh 2026-07-31 11:12:21 -04:00
Laan Tungir 75d983b2df v2.1.24 - Reactive caching service start/stop via LISTEN/NOTIFY, per-relay progress display, re-run/refresh caching controls 2026-07-31 10:08:45 -04:00
Laan Tungir a766f08738 v2.1.23 - Caching service: descriptive error reporting, retry limit, inbox poller fix, config reload fix, PHP admin page 2026-07-28 16:37:08 -04:00
Laan Tungir 23c37abe3a Update nostr_core_lib: increase receive timeout to 1000ms 2026-07-28 10:14:47 -04:00
Laan Tungir 5b3ccfd1f4 Update nostr_core_lib: handle NOTICE/CLOSED in synchronous_query 2026-07-28 09:39:22 -04:00
Laan Tungir 38a027e626 Update nostr_core_lib submodule: increase receive buffer to 256KB 2026-07-28 07:52:08 -04:00
Laan Tungir a042aca524 v2.1.22 - Added --start-caching and --reset-backfill CLI options, redirected caching_relay output to log file, increased backfill page size to 200 and max cap to 1000 for better completeness 2026-07-26 18:35:35 -04:00
Laan Tungir 20902e1c5d v2.1.21 - Fixed caching_service_binary_path to ./caching_relay (relative to relay CWD), added caching_relay build step to make_and_restart_relay.sh, added default caching config values (admin npub, 4 bootstrap relays, kinds 0 and 10002) 2026-07-26 14:56:16 -04:00
Laan Tungir 7233818da9 v2.1.20 - Added default caching config values: admin npub as root, 4 bootstrap relays, kinds 0 and 10002 added to default kinds list 2026-07-25 18:21:28 -04:00
Laan Tungir 86cbf7ff16 v2.1.19 - Consolidated caching_relay code into caching/ directory, switched launcher to PostgreSQL mode (-p pg_conn) so all config comes from the config table instead of a JSON config file 2026-07-25 16:27:56 -04:00
Laan Tungir 3035e71ca5 v2.1.18 - Fixed caching service integration: aligned caching_status response schema, fixed launcher to use -c config_path instead of --pg-conn, added caching service config fields to UI, replaced misleading not-implemented placeholder with accurate auth-failure messaging 2026-07-25 11:19:37 -04:00
Laan Tungir 179b160cf3 v2.1.17 - Verified api-worker LISTEN/NOTIFY monitoring on v2.1.16 relay (port 7777): subscriber-cadence, NOTIFY-reactive, and zero-overhead idle tests all pass 2026-07-25 10:17:30 -04:00
Laan Tungir aedd22436e v2.1.16 - Implemented api-worker timer + LISTEN/NOTIFY monitoring (Phases 1,2,5) and fixed thread-connection binding bug that stopped dashboard updates 2026-07-25 10:07:08 -04:00
Laan Tungir 8fdc362a8b v2.1.15 - Fix PG addressable d-tag derivation and replaceable/addressable transactional replace semantics 2026-05-01 19:01:06 -04:00
Laan Tungir 67cf3973c5 v2.1.14 - Fix PostgreSQL replaceable/addressable upsert semantics with NIP-01 conflict ordering 2026-05-01 14:38:57 -04:00
Laan Tungir c02ae2be5b v2.1.13 - Fix PostgreSQL startup detection for existing relays and make admin_pubkey parsing whitespace-tolerant 2026-04-03 16:36:56 -04:00
Laan Tungir fd3efdd01f v2.1.12 - Implement multi-admin auth cache and checks, add admin management commands, and add multi-admin integration test 2026-04-03 15:46:33 -04:00
Laan Tungir 232f93c16f v2.1.11 - Fix api-worker DB lifecycle and NIP test reliability: add dedicated worker DB connection, guard PG expiration cast in REQ path, correct NIP-45 baseline-aware expectation, and harden run_all_tests skip handling 2026-04-03 07:32:14 -04:00
Laan Tungir 9a9365dcfa v2.1.10 - Fix NIP-50 PG search semantics and NIP-17 test compatibility 2026-04-02 13:00:34 -04:00
Laan Tungir 5577b57149 v2.1.9 - Rebrand repository, docs, scripts, and tooling from c-relay to c-relay-pg 2026-04-02 07:06:41 -04:00
Laan Tungir 22c943d495 v2.1.8 - Implement explicit WAL checkpoint scheduling and disable SQLite auto-checkpoint 2026-04-01 21:03:24 -04:00
Laan Tungir 7e3d9b6825 v2.1.7 - Refactor SQLite connection ownership into db_ops, remove direct sqlite usage from runtime modules, and document agent-browser admin testing flow 2026-04-01 19:48:29 -04:00
Laan Tungir 547d22d09f v2.1.6 - Fix strict-mode admin API key access by prewarming config and relay private key caches 2026-04-01 19:08:42 -04:00
Laan Tungir ce9ae5a488 v2.1.5 - Offload duplicate and COUNT paths, add config/NIP-11 caching, and reduce DB reader threads 2026-04-01 16:15:46 -04:00
Laan Tungir 9b248b740f v2.1.4 - Harden async EVENT fallback handling and verify relay/nginx 503 behavior 2026-04-01 15:19:28 -04:00
Laan Tungir 0f77aeb72b v2.1.3 - Fix async EVENT thread safety by splitting store core/post-actions and naming lws main thread 2026-04-01 11:29:14 -04:00
Laan Tungir bb64651a0c v2.1.2 - Fix async REQ callback leak and validate Phase 2+3 subscription offload path 2026-04-01 11:01:38 -04:00
Laan Tungir 17be9c2b03 v2.1.1 - Wire REQ/COUNT/EVENT paths through thread pool sync helpers and stabilize filter limit tests 2026-04-01 09:38:25 -04:00
Laan Tungir f396c622b6 v2.1.0 - Bump to v2.1.0 as SQLite baseline for PostgreSQL fork handoff 2026-04-01 09:14:23 -04:00
Laan Tungir cbd260c1ae v2.0.4 - Complete Phase 1 DB abstraction: opaque db_stmt API, ip_ban migration, and SQLite leakage cleanup 2026-04-01 09:10:42 -04:00
Laan Tungir bc9ac290ab v2.0.3 - Complete Phase 1 SQLite abstraction cleanup across config/api/websockets and add db path accessor 2026-04-01 07:47:21 -04:00
Laan Tungir 192eeb248d v2.0.2 - Stabilize websocket queue draining, add db config count helper, and align NIP-45/50 test expectations 2026-04-01 07:04:58 -04:00
Laan Tungir da6c505420 v2.0.1 - Fix test-mode build/restart pipeline and NIP-11 root path handling; validate core NIP tests 2026-04-01 05:43:57 -04:00
Laan Tungir c077c209e5 v2.0.0 - Major architecture refactor: db_ops abstraction, low-risk SQLite refactor, and thread-pool scaffolding 2026-04-01 05:25:03 -04:00
Laan Tungir 9bf02702c2 v1.2.56 - Update codebase and documentation 2026-04-01 04:51:44 -04:00
Your Name 0920cc092d v1.2.55 - Preserve relay version metadata for API header/title and accept JSON NIP-11 content type 2026-03-23 09:09:50 -04:00
Your Name a89460be5d v1.2.54 - Sync relay_version to compiled CRELAY_VERSION on startup and show version in API header/title 2026-03-23 09:04:49 -04:00
Your Name ff2a3aa335 v1.2.53 - Fix P0 config-value memory leaks in hot paths and add investigation plan 2026-03-19 07:59:40 -04:00
Your Name 94b61e8a7c v1.2.52 - Update nostr_core_lib to v0.4.13 and route nostr logs through relay callback logger 2026-03-13 14:33:08 -04:00
Your Name b0c0754e83 v1.2.51 - Fix NIP-01 non-compliance: limit:0 now correctly returns zero stored events instead of falling through to default_limit 2026-03-04 11:23:58 -04:00
Your Name 3265e3d114 v1.2.50 - Fix NIP-42 relay URL verification and add onauth to all publish flows 2026-03-01 12:20:44 -04:00
Your Name 927659ece1 v1.2.49 - Fix: MEM% bar format (bar then MB), move CPU Core row after CPU Usage 2026-02-25 07:37:50 -04:00
Your Name b6ff4150b4 v1.2.48 - Add CPU% and MEM% ASCII bar graphs to API page stats 2026-02-25 07:33:06 -04:00
Your Name 63bc526163 v1.2.47 - Fix build: use getter function for g_connection_count (was static, can't extern) 2026-02-25 07:24:17 -04:00
Your Name a1f712236a v1.2.46 - Fix: move extern g_connection_count to file scope in api.c 2026-02-25 07:22:14 -04:00
Your Name 06e6c17b7b v1.2.45 - Add WebSocket Connections to system status: api.c sends active_connections, HTML+JS display it 2026-02-25 07:18:42 -04:00
Your Name 0751a7c55c v1.2.44 - IP Bans: show idle+auth bans in stats/status, fix filter buttons, add idle ban columns to query 2026-02-25 07:12:07 -04:00
Your Name f1728932a9 v1.2.43 - IP Bans page: fix filter buttons, add whitelist management UI, compact table rows 2026-02-25 07:08:20 -04:00
Your Name ef8bdef2a8 v1.2.42 - IP Bans page: fix filter buttons, add whitelist management UI, compact table rows 2026-02-25 07:08:12 -04:00
Your Name c11a8ba292 v1.2.41 - Fix IP Bans page: route ip_bans SQL responses to handleIpBansResponse 2026-02-25 06:59:22 -04:00
Your Name 0f124fe575 v1.2.40 - IP Bans page: show whitelisted IPs with star icon, hide Unban button for whitelisted IPs 2026-02-25 06:54:35 -04:00
Your Name 6b20452fab v1.2.39 - Fix IP Bans page: convert rows+columns SQL response format to objects for display 2026-02-25 06:53:50 -04:00
Your Name 10c19bc243 v1.2.38 - Add idle_ban_whitelist config: comma-separated IPs that are never idle-banned 2026-02-25 06:48:41 -04:00
Your Name 3d7aa2196f v1.2.37 - Fix: executeSqlQueryRaw uses relayPool/sendAdminCommand instead of undefined pool variable 2026-02-25 06:43:29 -04:00
Your Name a416c3f275 v1.2.36 - Fix: don't reset relay connection state when external relays time out in subscription onclose 2026-02-25 06:33:25 -04:00
Your Name bba9baabc3 v1.2.35 - Fix: only record idle ban failures for WebSocket connections, not HTTP requests (NIP-11/embedded files) 2026-02-24 17:41:59 -04:00
Your Name 31187c4c4f v1.2.34 - Fix: mark HTTP requests (NIP-11, embedded files) as session_active to prevent idle ban on legitimate HTTP clients 2026-02-24 17:07:33 -04:00
Your Name 55f862b879 v1.2.33 - Set default debug_level to 3 (INFO) 2026-02-24 16:06:19 -04:00
Your Name 76c9b3fcf0 v1.2.32 - Set idle_ban_threshold default to 1 and idle_ban_window_sec default to 30 2026-02-24 15:45:40 -04:00
Your Name 929bd09164 v1.2.31 - Fix idle connection timeout: use global connection list + periodic timer instead of lws_set_timeout which was not firing 2026-02-24 15:44:11 -04:00
Your Name d17f1dd8d5 v1.2.30 - Temporarily bypass admin verification on API page (idle timeout fix pending) 2026-02-24 14:55:39 -04:00
Your Name 207a949835 v1.2.29 - Added idle connection ban system - bans IPs that connect but never send REQ/EVENT (idle timeout or early disconnect) with separate rate limiting from auth failures 2026-02-24 14:18:27 -04:00
Your Name d08f4e4221 v1.2.28 - Add debug_level config setting: live update every 60s without restart, default 0 2026-02-24 08:56:32 -04:00
Your Name c96736fa6a v1.2.27 - Permanent ban escalation: ban_count never resets, IPs with history always get 24h ban on next failure 2026-02-23 18:18:55 -04:00
Your Name f8ec4ae924 v1.2.26 - Persistent IP ban table: save/load to ip_bans DB, auth success tracking, lifetime stats per IP 2026-02-23 18:16:14 -04:00
Your Name fe7304ac7f v1.2.25 - Add NIP-42 AUTH support to web UI: onauth callback in subscribeMany and publish calls 2026-02-23 18:03:29 -04:00
Your Name e5d39c984b v1.2.24 - Fix: run ip_ban maintenance unconditionally every 60s regardless of max_connection_seconds setting 2026-02-23 17:57:20 -04:00
Your Name 5e45f21e35 v1.2.23 - ip_ban: retain ban_count for 24 hours after last ban expiry, then fully clean entry 2026-02-23 17:16:02 -04:00
Your Name 5321a238b8 v1.2.22 - Fix ip_ban cleanup: preserve ban_count on cleanup so exponential backoff persists across ban expiry 2026-02-23 17:12:15 -04:00
Your Name 083bc14972 v1.2.21 - Fix IP ban check: use pss->client_ip (proxy-aware) instead of raw socket IP to match failure recording 2026-02-23 16:26:23 -04:00
Your Name 11aaccba9b v1.2.20 - Add IP auth failure ban system: track failures per IP, ban after threshold with exponential backoff, periodic log stats 2026-02-23 16:02:07 -04:00
Your Name bd1bbd763d v1.2.19 - Add IP auth failure ban system: track failures per IP, ban after threshold with exponential backoff, periodic log stats 2026-02-23 16:00:53 -04:00
Your Name 2bd7aa5a10 v1.2.18 - Proactive auth timeout via lws_set_timeout: close idle unauthenticated connections after nip42_auth_timeout_sec even without REQ 2026-02-23 15:37:45 -04:00
Your Name 361912ec85 v1.2.17 - Add nip42_auth_timeout_sec (default 10s): close unauthenticated connections after timeout to prevent connection accumulation 2026-02-23 15:22:18 -04:00
Your Name 0de491382e v1.2.16 - Fix auth rules UI: change label/placeholder/errors from nsec to npub (public key, not private key) 2026-02-23 14:36:19 -04:00
Your Name 3148bbbee7 v1.2.15 - Admin verification: show 'Waiting for response' with status updates, 60s timeout, no premature access denied 2026-02-23 14:26:02 -04:00
Your Name 3965ba04d8 v1.2.14 - Handle late admin verification response: grant access even if timeout already fired and access-denied overlay was shown 2026-02-23 14:24:55 -04:00
Your Name b96af938bd v1.2.13 - Add WoT status to NIP-11 response: restricted_writes, auth_required, and web_of_trust object when WoT enabled 2026-02-23 14:22:08 -04:00
Your Name 89c8248013 v1.2.12 - Increase admin verification timeout from 5s to 30s; continue waiting after publish timeout under heavy load 2026-02-23 14:16:56 -04:00
Your Name d8f477c6cf v1.2.11 - Early duplicate check before secp256k1 signature verification — skip crypto for events already in DB 2026-02-23 14:01:23 -04:00
Your Name 040eeadb13 v1.2.10 - Zero-copy message queue: eliminate memcpy in broadcast and REQ paths via queue_message_take_ownership() 2026-02-23 13:45:56 -04:00
Your Name 83f8b0ab88 v1.2.9 - Replace cJSON_GetObjectItem with CaseSensitive variant (178 calls) — eliminates 14% CPU from tolower+linear scan in hot path 2026-02-23 13:25:50 -04:00
Your Name b82f7eaaf3 v1.2.8 - Add debug build support: _debug suffix in build_static.sh, deploy_lt_debug.sh with perf profiling workflow 2026-02-23 13:07:53 -04:00
Your Name 0dc5b75d7c v1.2.7 - Add configurable SQLite mmap_size (256MB) and cache_size (64MB) PRAGMAs for ~20% CPU reduction from DB read overhead 2026-02-23 09:48:01 -04:00
Your Name e94b1a81e3 v1.2.6 - Add MAX_MESSAGE_QUEUE_SIZE=500 limit to prevent OOM from unbounded write queue; fix wasted buf allocation in broadcast 2026-02-23 09:12:09 -04:00
Your Name 1adabdbc4e v1.2.5 - Add nip17_admin_enabled config toggle (default off) to prevent OOM from NIP-17 gift wrap decryption flood 2026-02-23 08:58:10 -04:00
Your Name 81d44c3d8c v1.2.4 - Add more characters to valid subscription characters 2026-02-11 05:56:09 -04:00
285 changed files with 86030 additions and 3311 deletions
+1
View File
@@ -11,3 +11,4 @@ copy_executable_local.sh
nostr_login_lite/
style_guide/
nostr-tools
.test_keysadmin/cache/
-3
View File
@@ -1,6 +1,3 @@
[submodule "nostr_core_lib"]
path = nostr_core_lib
url = https://git.laantungir.net/laantungir/nostr_core_lib.git
[submodule "c_utils_lib"]
path = c_utils_lib
url = ssh://git@git.laantungir.net:2222/laantungir/c_utils_lib.git
+2
View File
@@ -0,0 +1,2 @@
ADMIN_PUBKEY='6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3'
SERVER_PRIVKEY='1111111111111111111111111111111111111111111111111111111111111111'
+11 -11
View File
@@ -1,12 +1,12 @@
# AGENTS.md - AI Agent Integration Guide for Architect Mode
**Project-Specific Information for AI Agents Working with C-Relay in Architect Mode**
**Project-Specific Information for AI Agents Working with C-Relay-PG in Architect Mode**
## Critical Architecture Understanding
### System Architecture Overview
C-Relay implements a **unique event-based configuration architecture** that fundamentally differs from traditional Nostr relays:
C-Relay-PG implements a **unique event-based configuration architecture** that fundamentally differs from traditional Nostr relays:
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
@@ -51,9 +51,9 @@ C-Relay implements a **unique event-based configuration architecture** that fund
## Architectural Decision Analysis
### Configuration System Design
**Traditional Approach vs C-Relay:**
**Traditional Approach vs C-Relay-PG:**
```
Traditional: C-Relay:
Traditional: C-Relay-PG:
config.json → kind 33334 events
ENV variables → cryptographically signed tags
File watching → database polling/restart
@@ -119,18 +119,18 @@ File watching → database polling/restart
```
Developer Machine:
├── ./make_and_restart_relay.sh
├── build/c_relay_x86
├── build/c_relay_pg_x86
├── build/<relay_pubkey>.db
└── relay.log
```
### Production SystemD Deployment
```
/opt/c-relay/:
├── c_relay_x86
/opt/c-relay-pg/:
├── c_relay_pg_x86
├── <relay_pubkey>.db
├── systemd service (c-relay.service)
└── c-relay user isolation
├── systemd service (c-relay-pg.service)
└── c-relay-pg user isolation
```
### Container Deployment Architecture
@@ -144,7 +144,7 @@ Container:
### Reverse Proxy Architecture
```
Internet → Nginx/HAProxy → C-Relay
Internet → Nginx/HAProxy → C-Relay-PG
├── WebSocket upgrade handling
├── SSL termination
└── Rate limiting
@@ -292,7 +292,7 @@ Admin Signs Event → WebSocket Submit → Validate → Store → Restart Requir
**Decision**: Same port serves both protocols
**Consequences**: Simplified deployment, protocol detection overhead, libwebsockets dependency
These architectural decisions form the foundation of C-Relay's unique approach to Nostr relay implementation and should be carefully considered when planning extensions or modifications.
These architectural decisions form the foundation of C-Relay-PG's unique approach to Nostr relay implementation and should be carefully considered when planning extensions or modifications.
**
[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]
+2
View File
@@ -0,0 +1,2 @@
ADMIN_PUBKEY='8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e'
SERVER_PRIVKEY='1111111111111111111111111111111111111111111111111111111111111111'
Executable
BIN
View File
Binary file not shown.
+61 -8
View File
@@ -1,6 +1,59 @@
# AGENTS.md - AI Agent Integration Guide
**Project-Specific Information for AI Agents Working with C-Relay**
**Project-Specific Information for AI Agents Working with C-Relay-PG**
## ⚠️ CRITICAL: Never Run `make` Directly
**NEVER run `make` to build the relay binary.** The Makefile will refuse and
print an error. Running `make` directly produces a dynamically-linked binary
that will silently fall back to SQLite storage while the admin UI connects to
PostgreSQL — causing the admin page to show stale data.
Always use one of these instead:
- **`./build_static.sh`** — Build the static MUSL binary with PostgreSQL backend
- **`./make_and_restart_relay.sh`** — Build, kill old relay, and start new one
The Makefile exists only for submodule compilation (nostr_core_lib, c_utils_lib)
and utility targets (clean, install-deps). It will refuse to build the relay
binary directly.
## Critical Build Commands
### Primary Build Command
```bash
./make_and_restart_relay.sh
```
**Never use `make` directly.** The project requires the custom restart script which:
- Handles database preservation/cleanup based on flags
- Manages architecture-specific binary detection (x86/ARM64)
- Performs automatic process cleanup and port management
- Starts relay in background with proper logging
**Note:** `--test-keys` / `-t` is now the **default**. Use `--no-test-keys` or `--production` for random key generation.
### Architecture-Specific Binary Outputs
- **x86_64**: `./build/c_relay_pg_x86`
- **ARM64**: `./build/c_relay_pg_arm64`
- **Other**: `./build/c_relay_pg_$(ARCH)`
**Project-Specific Information for AI Agents Working with C-Relay-PG**
## ⚠️ CRITICAL: Never Run `make` Directly
**NEVER run `make` to build the relay binary.** The Makefile will refuse and
print an error. Running `make` directly produces a dynamically-linked binary
that will silently fall back to SQLite storage while the admin UI connects to
PostgreSQL — causing the admin page to show stale data.
Always use one of these instead:
- **`./build_static.sh`** — Build the static MUSL binary with PostgreSQL backend
- **`./make_and_restart_relay.sh`** — Build, kill old relay, and start new one
The Makefile exists only for submodule compilation (nostr_core_lib, c_utils_lib)
and utility targets (clean, install-deps). It will refuse to build the relay
binary directly.
## Critical Build Commands
@@ -15,9 +68,9 @@
- Starts relay in background with proper logging
### Architecture-Specific Binary Outputs
- **x86_64**: `./build/c_relay_x86`
- **ARM64**: `./build/c_relay_arm64`
- **Other**: `./build/c_relay_$(ARCH)`
- **x86_64**: `./build/c_relay_pg_x86`
- **ARM64**: `./build/c_relay_pg_arm64`
- **Other**: `./build/c_relay_pg_$(ARCH)`
### Database File Naming Convention
- **Format**: `<relay_pubkey>.db` (NOT `.nrdb` as shown in docs)
@@ -75,10 +128,10 @@
### Process Management
```bash
# Kill existing relay processes
pkill -f "c_relay_"
pkill -f "c_relay_pg_"
# Check running processes
ps aux | grep c_relay_
ps aux | grep c_relay_pg_
# Force kill port binding
fuser -k 8888/tcp
@@ -95,7 +148,7 @@ fuser -k 8888/tcp
- Event configuration tests: `tests/event_config_tests.sh`
### SystemD Integration Considerations
- Service runs as `c-relay` user in `/opt/c-relay`
- Service runs as `c-relay-pg` user in `/opt/c-relay-pg`
- Database files created in WorkingDirectory automatically
- No environment variables needed (event-based config)
- Resource limits: 65536 file descriptors, 4096 processes
@@ -137,7 +190,7 @@ fuser -k 8888/tcp
## Quick Debugging Commands
```bash
# Check relay status
ps aux | grep c_relay_ && netstat -tln | grep 8888
ps aux | grep c_relay_pg_ && netstat -tln | grep 8888
# View logs
tail -f relay.log
+9 -9
View File
@@ -1,6 +1,6 @@
# C-Relay API Documentation
# C-Relay-PG API Documentation
Complete API reference for the C-Relay event-based administration system and advanced features.
Complete API reference for the C-Relay-PG event-based administration system and advanced features.
## Table of Contents
@@ -21,7 +21,7 @@ Complete API reference for the C-Relay event-based administration system and adv
## Overview
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as cryptographically signed Nostr events. This provides:
C-Relay-PG uses an innovative **event-based administration system** where all configuration and management commands are sent as cryptographically signed Nostr events. This provides:
- **Cryptographic security**: All commands must be signed with the admin private key
- **Audit trail**: Complete history of all administrative actions
@@ -63,8 +63,8 @@ Store the admin private key securely:
export C_RELAY_ADMIN_KEY="nsec1abc123..."
# Secure file
echo "nsec1abc123..." > ~/.c-relay-admin
chmod 600 ~/.c-relay-admin
echo "nsec1abc123..." > ~/.c-relay-pg-admin
chmod 600 ~/.c-relay-pg-admin
# Password manager (recommended)
# Store in 1Password, Bitwarden, etc.
@@ -167,7 +167,7 @@ Examples:
"data": [
{
"key": "relay_name",
"value": "C-Relay",
"value": "C-Relay-PG",
"data_type": "string",
"category": "relay",
"description": "Relay name displayed in NIP-11"
@@ -506,10 +506,10 @@ ORDER BY count DESC
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `relay_name` | string | "C-Relay" | Relay name (NIP-11) |
| `relay_name` | string | "C-Relay-PG" | Relay name (NIP-11) |
| `relay_description` | string | "C Nostr Relay" | Relay description |
| `relay_contact` | string | "" | Admin contact info |
| `relay_software` | string | "c-relay" | Software identifier |
| `relay_software` | string | "c-relay-pg" | Software identifier |
| `relay_version` | string | auto | Software version |
| `supported_nips` | string | "1,9,11,13,15,20,33,40,42,45,50,70" | Supported NIPs |
| `language_tags` | string | "*" | Supported languages |
@@ -576,7 +576,7 @@ ORDER BY count DESC
## Real-time Monitoring
C-Relay provides subscription-based real-time monitoring using ephemeral events (kind 24567).
C-Relay-PG provides subscription-based real-time monitoring using ephemeral events (kind 24567).
### Activation
+37 -32
View File
@@ -1,11 +1,12 @@
# Alpine-based MUSL static binary builder for C-Relay
# Alpine-based MUSL static binary builder for C-Relay-PG
# Produces truly portable binaries with zero runtime dependencies
# PostgreSQL backend only.
ARG DEBUG_BUILD=false
FROM alpine:3.19 AS builder
# Re-declare build argument in this stage
# Re-declare build arguments in this stage
ARG DEBUG_BUILD=false
# Install build dependencies
@@ -26,9 +27,11 @@ RUN apk add --no-cache \
curl-static \
sqlite-dev \
sqlite-static \
postgresql-dev \
linux-headers \
wget \
bash
bash \
openssh-client
# Set working directory
WORKDIR /build
@@ -68,15 +71,8 @@ RUN cd /tmp && \
make install && \
rm -rf /tmp/libwebsockets
# Copy only submodule configuration and git directory
COPY .gitmodules /build/.gitmodules
COPY .git /build/.git
# Clean up any stale submodule references (nips directory is not a submodule)
RUN git rm --cached nips 2>/dev/null || true
# Initialize submodules (cached unless .gitmodules changes)
RUN git submodule update --init --recursive
# Submodules are provided in the local build context and copied explicitly below.
# Avoid network/SSH dependency inside Docker image build.
# Copy nostr_core_lib source files (cached unless nostr_core_lib changes)
COPY nostr_core_lib /build/nostr_core_lib/
@@ -84,59 +80,68 @@ COPY nostr_core_lib /build/nostr_core_lib/
# Copy c_utils_lib source files (cached unless c_utils_lib changes)
COPY c_utils_lib /build/c_utils_lib/
# Build c_utils_lib with MUSL-compatible flags (cached unless c_utils_lib changes)
# Build c_utils_lib static library for relay logging utilities
# (build only debug.c to avoid broken/missing version header surface in submodule revision)
RUN cd c_utils_lib && \
sed -i 's/CFLAGS = -Wall -Wextra -std=c99 -O2 -g/CFLAGS = -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g/' Makefile && \
make clean && \
make
mkdir -p build && \
gcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g \
-Isrc -Iinclude -c src/debug.c -o build/debug.o && \
ar rcs libc_utils.a build/debug.o
# Build nostr_core_lib with required NIPs (cached unless nostr_core_lib changes)
# Disable fortification in build.sh to prevent __*_chk symbol issues
# NIPs: 001(Basic), 006(Keys), 013(PoW), 017(DMs), 019(Bech32), 044(Encryption), 059(Gift Wrap - required by NIP-17)
# NIPs: 001(Basic), 004(Encryption), 006(Keys), 013(PoW), 017(DMs), 019(Bech32), 044(Encryption), 059(Gift Wrap - required by NIP-17)
# NIP-04 is required by nostr_signer.c (NIP-46 bunker signer uses NIP-04 encryption)
RUN cd nostr_core_lib && \
chmod +x build.sh && \
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
rm -f *.o *.a 2>/dev/null || true && \
./build.sh --nips=1,6,13,17,19,44,59
./build.sh --nips=1,4,6,13,17,19,44,59
# Copy c-relay source files LAST (only this layer rebuilds on source changes)
# Copy c-relay-pg source files LAST (only this layer rebuilds on source changes)
COPY src/ /build/src/
COPY Makefile /build/Makefile
# Build c-relay with full static linking (only rebuilds when src/ changes)
# Build c-relay-pg with full static linking (only rebuilds when src/ changes)
# Disable fortification to avoid __*_chk symbols that don't exist in MUSL
# Use conditional compilation flags based on DEBUG_BUILD argument
# Use conditional compilation flags based on DEBUG_BUILD build arg.
# PostgreSQL backend is always used.
RUN if [ "$DEBUG_BUILD" = "true" ]; then \
CFLAGS="-g -O2 -DDEBUG"; \
STRIP_CMD="echo 'Keeping debug symbols'"; \
echo "Building with DEBUG symbols enabled (optimized with -O2)"; \
else \
CFLAGS="-O2"; \
STRIP_CMD="strip /build/c_relay_static"; \
STRIP_CMD="strip /build/c_relay_pg_static"; \
echo "Building optimized production binary (symbols stripped)"; \
fi && \
gcc -static $CFLAGS -Wall -Wextra -std=c99 \
DB_FLAGS="-DDB_BACKEND_POSTGRES -DHAVE_LIBPQ" && \
DB_LIBS="-lpq -lpgcommon -lpgport" && \
echo "Compiling with PostgreSQL backend" && \
gcc -static $CFLAGS $DB_FLAGS -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket -I/usr/include/postgresql \
src/main.c src/config.c src/dm_admin.c src/request_validator.c \
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
-o /build/c_relay_static \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c \
src/caching_inbox_poller.c src/caching_service_launcher.c \
src/db_ops.c src/db_ops_postgres.c src/thread_pool.c \
-o /build/c_relay_pg_static \
c_utils_lib/libc_utils.a \
nostr_core_lib/libnostr_core_x64.a \
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl && \
-lwebsockets -lssl -lcrypto -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl $DB_LIBS && \
eval "$STRIP_CMD"
# Verify it's truly static
RUN echo "=== Binary Information ===" && \
file /build/c_relay_static && \
ls -lh /build/c_relay_static && \
file /build/c_relay_pg_static && \
ls -lh /build/c_relay_pg_static && \
echo "=== Checking for dynamic dependencies ===" && \
(ldd /build/c_relay_static 2>&1 || echo "Binary is static") && \
(ldd /build/c_relay_pg_static 2>&1 || echo "Binary is static") && \
echo "=== Build complete ==="
# Output stage - just the binary
FROM scratch AS output
COPY --from=builder /build/c_relay_static /c_relay_static
COPY --from=builder /build/c_relay_pg_static /c_relay_pg_static
+83 -131
View File
@@ -1,32 +1,56 @@
# C-Relay Makefile
# C-Relay-PG Makefile
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -g -O2
INCLUDES = -I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket
LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl -Lc_utils_lib -lc_utils
CFLAGS = -Wall -Wextra -std=c99 -g -O2 -DDB_BACKEND_POSTGRES -DHAVE_LIBPQ
INCLUDES = -I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket -I/usr/include/postgresql
LIBS = -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl -Lc_utils_lib -lc_utils -lpq
# Build directory
BUILD_DIR = build
# Source files
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c src/thread_pool.c src/caching_inbox_poller.c src/caching_service_launcher.c
DB_OPS_SRC = src/db_ops.c src/db_ops_postgres.c
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
C_UTILS_LIB = c_utils_lib/libc_utils.a
# Architecture detection
ARCH = $(shell uname -m)
ifeq ($(ARCH),x86_64)
TARGET = $(BUILD_DIR)/c_relay_x86
TARGET = $(BUILD_DIR)/c_relay_pg_x86
else ifeq ($(ARCH),aarch64)
TARGET = $(BUILD_DIR)/c_relay_arm64
TARGET = $(BUILD_DIR)/c_relay_pg_arm64
else ifeq ($(ARCH),arm64)
TARGET = $(BUILD_DIR)/c_relay_arm64
TARGET = $(BUILD_DIR)/c_relay_pg_arm64
else
TARGET = $(BUILD_DIR)/c_relay_$(ARCH)
TARGET = $(BUILD_DIR)/c_relay_pg_$(ARCH)
endif
# Default target
all: $(TARGET)
# Default target — refuse direct build, instruct to use build_static.sh
# The Makefile is only for submodule builds (nostr_core_lib, c_utils_lib)
# and utility targets (clean, install-deps, etc.).
# Running 'make' directly produces a dynamically-linked binary that will
# silently fall back to SQLite storage while the admin UI connects to
# PostgreSQL — causing the admin page to show stale data.
all:
@echo "============================================"
@echo " ERROR: Do not run 'make' directly!"
@echo ""
@echo " This project requires a static MUSL build"
@echo " with PostgreSQL backend. Run:"
@echo ""
@echo " ./build_static.sh"
@echo ""
@echo " Or use the full build+restart script:"
@echo ""
@echo " ./make_and_restart_relay.sh"
@echo ""
@echo " The Makefile is only for submodule builds"
@echo " (nostr_core_lib, c_utils_lib) and utility"
@echo " targets (clean, install-deps, etc.)."
@echo "============================================"
@exit 1
# Create build directory
$(BUILD_DIR):
@@ -80,147 +104,75 @@ force-version:
@echo "Force updating main.h version information..."
@$(MAKE) src/main.h
# Build the relay
$(TARGET): $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) $(C_UTILS_LIB)
@echo "Compiling C-Relay for architecture: $(ARCH)"
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(TARGET) $(NOSTR_CORE_LIB) $(C_UTILS_LIB) $(LIBS)
@echo "Build complete: $(TARGET)"
# Build the relay — guarded to prevent accidental use.
# Use ./build_static.sh instead.
$(TARGET):
@echo "ERROR: Do not run 'make $(TARGET)' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
# Build for specific architectures
x86: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) $(C_UTILS_LIB)
@echo "Building C-Relay for x86_64..."
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_x86 $(NOSTR_CORE_LIB) $(C_UTILS_LIB) $(LIBS)
@echo "Build complete: $(BUILD_DIR)/c_relay_x86"
x86:
@echo "ERROR: Do not run 'make x86' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
arm64: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) $(C_UTILS_LIB)
@echo "Cross-compiling C-Relay for ARM64..."
arm64:
@echo "ERROR: Do not run 'make arm64' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
@if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
echo "ERROR: ARM64 cross-compiler not found."; \
echo "Install with: make install-cross-tools"; \
echo "Or install manually: sudo apt install gcc-aarch64-linux-gnu"; \
echo "ERROR: aarch64-linux-gnu-gcc not found."; \
echo "Install the ARM64 cross-compiler:"; \
echo " sudo apt install gcc-aarch64-linux-gnu"; \
exit 1; \
fi
@echo "Checking for ARM64 development libraries..."
@if ! dpkg -l | grep -q "libssl-dev:arm64\|libsqlite3-dev:arm64"; then \
@if ! dpkg -l | grep -q "libssl-dev:arm64\|libpq-dev:arm64"; then \
echo "ERROR: ARM64 libraries not found. Cross-compilation requires ARM64 versions of:"; \
echo " - libssl-dev:arm64"; \
echo " - libsqlite3-dev:arm64"; \
echo " - libpq-dev:arm64"; \
echo " - libwebsockets-dev:arm64"; \
echo " - libsecp256k1-dev:arm64"; \
echo " - zlib1g-dev:arm64"; \
echo " - libcurl4-openssl-dev:arm64"; \
echo " - libcurl-dev:arm64"; \
echo ""; \
echo "Install ARM64 libraries with: make install-arm64-deps"; \
echo "Or use Docker for cross-platform builds."; \
echo "Install them with:"; \
echo " sudo dpkg --add-architecture arm64"; \
echo " sudo apt update"; \
echo " sudo apt install gcc-aarch64-linux-gnu \\"; \
echo " libssl-dev:arm64 libpq-dev:arm64 \\"; \
echo " libwebsockets-dev:arm64 zlib1g-dev:arm64 \\"; \
echo " libcurl4-openssl-dev:arm64"; \
exit 1; \
fi
@echo "Using aarch64-linux-gnu-gcc with ARM64 libraries..."
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \
aarch64-linux-gnu-gcc $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_arm64 $(NOSTR_CORE_LIB) $(C_UTILS_LIB) \
-L/usr/lib/aarch64-linux-gnu $(LIBS)
@echo "Build complete: $(BUILD_DIR)/c_relay_arm64"
fi; \
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig \
PKG_CONFIG_SYSROOT_DIR=/ \
aarch64-linux-gnu-gcc -Wall -Wextra -std=c99 -g -O2 \
-DDB_BACKEND_POSTGRES -DHAVE_LIBPQ \
-I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
$(MAIN_SRC) $(DB_OPS_SRC) \
-o $(BUILD_DIR)/c_relay_pg_arm64 \
$(NOSTR_CORE_LIB) $(C_UTILS_LIB) \
-lwebsockets -lssl -lcrypto -lz -ldl -lpthread -lm \
-lsecp256k1 -lcurl -lpq
@echo "Cross-compilation complete: $(BUILD_DIR)/c_relay_pg_arm64"
# Install ARM64 cross-compilation dependencies
# Install dependencies for ARM64 cross-compilation
install-arm64-deps:
@echo "Installing ARM64 cross-compilation dependencies..."
@echo "This requires adding ARM64 architecture and installing cross-libraries..."
sudo dpkg --add-architecture arm64
sudo apt update
sudo apt install -y \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
libssl-dev:arm64 \
libsqlite3-dev:arm64 \
zlib1g-dev:arm64 \
sudo apt install -y gcc-aarch64-linux-gnu \
libssl-dev:arm64 libpq-dev:arm64 \
libwebsockets-dev:arm64 zlib1g-dev:arm64 \
libcurl4-openssl-dev:arm64
@echo "Note: libwebsockets-dev:arm64 and libsecp256k1-dev:arm64 may need manual building"
# Install cross-compilation tools
install-cross-tools:
@echo "Installing cross-compilation tools..."
# Install dependencies for native build
install-deps:
sudo apt update
sudo apt install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
# Check what architectures we can actually build
check-toolchain:
@echo "Checking available toolchains:"
@echo "Native compiler: $(shell $(CC) --version | head -1)"
@if command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
echo "ARM64 cross-compiler: $(shell aarch64-linux-gnu-gcc --version | head -1)"; \
else \
echo "ARM64 cross-compiler: NOT INSTALLED (install with 'make install-cross-tools')"; \
fi
# Run tests
test: $(TARGET)
@echo "Running tests..."
./tests/1_nip_test.sh
# Initialize database (now handled automatically when server starts)
init-db:
@echo "Database initialization is now handled automatically when the server starts."
@echo "The schema is embedded in the binary - no external files needed."
@echo "To manually recreate database: rm -f db/c_nostr_relay.db && ./build/c_relay_x86"
sudo apt install -y build-essential libpq-dev libssl-dev libcurl4-openssl-dev libsecp256k1-dev zlib1g-dev jq curl
# Clean build artifacts
clean:
rm -rf $(BUILD_DIR)
@echo "Clean complete"
# Clean everything including nostr_core_lib and c_utils_lib
clean-all: clean
cd nostr_core_lib && make clean 2>/dev/null || true
cd c_utils_lib && make clean 2>/dev/null || true
# Install dependencies (Ubuntu/Debian)
install-deps:
@echo "Installing dependencies..."
sudo apt update
sudo apt install -y build-essential libsqlite3-dev libssl-dev libcurl4-openssl-dev libsecp256k1-dev zlib1g-dev jq curl
# Help
help:
@echo "C-Relay Build System"
@echo ""
@echo "Targets:"
@echo " all Build the relay for current architecture (default)"
@echo " x86 Build specifically for x86_64"
@echo " arm64 Build for ARM64 (requires cross-compilation setup)"
@echo " test Build and run tests"
@echo " init-db Initialize the database"
@echo " clean Clean build artifacts"
@echo " clean-all Clean everything including dependencies"
@echo " install-deps Install system dependencies"
@echo " install-cross-tools Install basic ARM64 cross-compiler"
@echo " install-arm64-deps Install ARM64 cross-compilation libraries"
@echo " check-toolchain Check available compilers"
@echo " help Show this help"
@echo ""
@echo "Usage:"
@echo " make # Build the relay for current arch"
@echo " make x86 # Build for x86_64"
@echo " make arm64 # Build for ARM64 (fails if cross-compilation not set up)"
@echo " make install-arm64-deps # Install full ARM64 cross-compilation setup"
@echo " make check-toolchain # Check what compilers are available"
@echo " make test # Run tests"
@echo " make init-db # Set up database"
@echo " make force-version # Force regenerate main.h from git"
# Build fully static MUSL binaries using Docker
static-musl-x86_64:
@echo "Building fully static MUSL binary for x86_64..."
docker buildx build --platform linux/amd64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-x86_64 --load .
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-x86_64 sh -c "cp /c_relay_static_musl_x86_64 /output/"
@echo "Static binary created: build/c_relay_static_musl_x86_64"
static-musl-arm64:
@echo "Building fully static MUSL binary for ARM64..."
docker buildx build --platform linux/arm64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-arm64 --load .
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-arm64 sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
@echo "Static binary created: build/c_relay_static_musl_arm64"
static-musl: static-musl-x86_64 static-musl-arm64
@echo "Built static MUSL binaries for both architectures"
.PHONY: static-musl-x86_64 static-musl-arm64 static-musl
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
.PHONY: all x86 arm64 install-arm64-deps install-deps clean force-version
+6 -6
View File
@@ -45,21 +45,21 @@ Also included is a more standard administrative web front end. This front end co
## Screenshots
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/main.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/main.png)
Main page with real time updates.
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/config.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/config.png)
Set your configuration preferences.
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/subscriptions.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/subscriptions.png)
View current subscriptions
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/white-blacklists.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/white-blacklists.png)
Add npubs to white or black lists.
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/sqlQuery.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/sqlQuery.png)
Run sql queries on the database.
![](https://git.laantungir.net/laantungir/c-relay/raw/branch/master/screenshots/main-light.png)
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/main-light.png)
Light mode.
+24 -24
View File
@@ -1,11 +1,11 @@
# C-Relay: High-Performance Nostr Relay
# C-Relay-PG: High-Performance Nostr Relay
A blazingly fast, production-ready Nostr relay implemented in C with an innovative event-based configuration system. Built for performance, security, and ease of deployment.
## 🚀 Why C-Relay?
## 🚀 Why C-Relay-PG?
### Event-Based Configuration
Unlike traditional relays that require config files, C-Relay uses **cryptographically signed Nostr events** for all configuration. This means:
Unlike traditional relays that require config files, C-Relay-PG uses **cryptographically signed Nostr events** for all configuration. This means:
- **Zero config files** - Everything stored in the database
- **Real-time updates** - Changes applied instantly without restart
- **Cryptographic security** - All changes must be signed by admin
@@ -36,7 +36,7 @@ Control your relay by sending direct messages from any Nostr client:
## 📋 Supported NIPs
C-Relay implements a comprehensive set of Nostr Improvement Proposals:
C-Relay-PG implements a comprehensive set of Nostr Improvement Proposals:
-**NIP-01**: Basic protocol flow implementation
-**NIP-09**: Event deletion
@@ -89,12 +89,12 @@ Download and run - no dependencies required:
```bash
# Download the latest static release
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
chmod +x c-relay-v0.6.0-linux-x86_64-static
mv c-relay-v0.6.0-linux-x86_64-static c-relay
wget https://git.laantungir.net/laantungir/c-relay-pg/releases/download/v0.6.0/c-relay-pg-v0.6.0-linux-x86_64-static
chmod +x c-relay-pg-v0.6.0-linux-x86_64-static
mv c-relay-pg-v0.6.0-linux-x86_64-static c-relay-pg
# Run the relay
./c-relay
./c-relay-pg
```
**Important**: On first startup, save the **Admin Private Key** displayed in the console. You'll need it for all administrative operations.
@@ -107,8 +107,8 @@ sudo apt install -y build-essential git sqlite3 libsqlite3-dev \
libwebsockets-dev libssl-dev libsecp256k1-dev libcurl4-openssl-dev zlib1g-dev
# Clone and build
git clone https://github.com/your-org/c-relay.git
cd c-relay
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive
./make_and_restart_relay.sh
```
@@ -136,8 +136,8 @@ The web interface provides:
```bash
# Clone repository
git clone https://github.com/your-org/c-relay.git
cd c-relay
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive
# Build
@@ -147,25 +147,25 @@ make clean && make
sudo systemd/install-service.sh
# Start and enable
sudo systemctl start c-relay
sudo systemctl enable c-relay
sudo systemctl start c-relay-pg
sudo systemctl enable c-relay-pg
# Capture admin keys from logs
sudo journalctl -u c-relay | grep "Admin Private Key"
sudo journalctl -u c-relay-pg | grep "Admin Private Key"
```
### Docker Deployment
```bash
# Build Docker image
docker build -f Dockerfile.alpine-musl -t c-relay .
docker build -f Dockerfile.alpine-musl -t c-relay-pg .
# Run container
docker run -d \
--name c-relay \
--name c-relay-pg \
-p 8888:8888 \
-v /path/to/data:/data \
c-relay
c-relay-pg
```
### Cloud Deployment
@@ -181,7 +181,7 @@ See [`docs/deployment_guide.md`](docs/deployment_guide.md) for detailed deployme
## 🔧 Configuration
C-Relay uses an innovative event-based configuration system. All settings are managed through signed Nostr events.
C-Relay-PG uses an innovative event-based configuration system. All settings are managed through signed Nostr events.
### Basic Configuration
@@ -239,8 +239,8 @@ The admin private key is displayed **only once** during first startup. Store it
```bash
# Save to secure location
echo "ADMIN_PRIVKEY=your_admin_private_key" > ~/.c-relay-admin
chmod 600 ~/.c-relay-admin
echo "ADMIN_PRIVKEY=your_admin_private_key" > ~/.c-relay-pg-admin
chmod 600 ~/.c-relay-pg-admin
```
### Production Security
@@ -269,9 +269,9 @@ Contributions are welcome! Please:
## 🔗 Links
- **Repository**: [https://github.com/your-org/c-relay](https://github.com/your-org/c-relay)
- **Releases**: [https://git.laantungir.net/laantungir/c-relay/releases](https://git.laantungir.net/laantungir/c-relay/releases)
- **Issues**: [https://github.com/your-org/c-relay/issues](https://github.com/your-org/c-relay/issues)
- **Repository**: [https://github.com/your-org/c-relay-pg](https://github.com/your-org/c-relay-pg)
- **Releases**: [https://git.laantungir.net/laantungir/c-relay-pg/releases](https://git.laantungir.net/laantungir/c-relay-pg/releases)
- **Issues**: [https://github.com/your-org/c-relay-pg/issues](https://github.com/your-org/c-relay-pg/issues)
- **Nostr Protocol**: [https://github.com/nostr-protocol/nostr](https://github.com/nostr-protocol/nostr)
## 💬 Support
+7 -7
View File
@@ -41,7 +41,7 @@ This guide is specifically tailored for C programs that use:
```bash
# 1. Copy the Dockerfile template (see below)
cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static
cp /path/to/c-relay-pg/Dockerfile.alpine-musl ./Dockerfile.static
# 2. Customize for your project (see Customization section)
vim Dockerfile.static
@@ -466,13 +466,13 @@ docker build -t my-app:latest .
docker run --rm my-app:latest --help
```
## Reusing c-relay Files
## Reusing c-relay-pg Files
You can directly copy these files from c-relay:
You can directly copy these files from c-relay-pg:
### 1. Dockerfile.alpine-musl
```bash
cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static
cp /path/to/c-relay-pg/Dockerfile.alpine-musl ./Dockerfile.static
```
Then customize:
@@ -482,7 +482,7 @@ Then customize:
### 2. build_static.sh
```bash
cp /path/to/c-relay/build_static.sh ./
cp /path/to/c-relay-pg/build_static.sh ./
```
Then customize:
@@ -492,7 +492,7 @@ Then customize:
### 3. .dockerignore (Optional)
```bash
cp /path/to/c-relay/.dockerignore ./
cp /path/to/c-relay-pg/.dockerignore ./
```
Helps speed up Docker builds by excluding unnecessary files.
@@ -522,7 +522,7 @@ Helps speed up Docker builds by excluding unnecessary files.
- [Alpine Linux](https://alpinelinux.org/)
- [nostr_core_lib](https://github.com/chebizarro/nostr_core_lib)
- [Static Linking Best Practices](https://www.musl-libc.org/faq.html)
- [c-relay Implementation](./docs/musl_static_build.md)
- [c-relay-pg Implementation](./docs/musl_static_build.md)
## Example: Minimal Nostr Client
+37
View File
@@ -0,0 +1,37 @@
<?php
/** admin2/api/auth.php — Auth rules + WoT status. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
try {
if ($action === 'add') {
$pdo->prepare("INSERT INTO auth_rules (rule_type, pattern_type, pattern_value) VALUES (?, 'pubkey', ?)")->execute([$input['rule_type'], $input['pattern_value']]);
json_response(['message' => 'Rule added']);
} elseif ($action === 'remove') {
$pdo->prepare("DELETE FROM auth_rules WHERE id = ?")->execute([intval($input['id'])]);
json_response(['message' => 'Rule removed']);
} elseif ($action === 'set_wot_level') {
$pdo->prepare("UPDATE config SET value = ? WHERE key = 'wot_level'")->execute([strval($input['level'])]);
json_response(['message' => 'WoT level set']);
} elseif ($action === 'sync_wot') {
json_response(['message' => 'WoT sync not available via PHP (requires relay)']);
}
} catch (PDOException $e) { json_response(['error' => $e->getMessage()]); }
}
// GET: WoT status
if (isset($_GET['action']) && $_GET['action'] === 'wot') {
$level = $pdo->query("SELECT value FROM config WHERE key = 'wot_level'")->fetchColumn() ?: '0';
$wl_count = 0;
try { $wl_count = intval($pdo->query("SELECT count(*) FROM auth_rules WHERE rule_type = 'whitelist'")->fetchColumn()); } catch (PDOException $e) {}
$descriptions = ['0' => 'Level 0: Open relay — anyone can read and write', '1' => 'Level 1: Write only — WoT users can write', '2' => 'Level 2: Full — WoT users only'];
json_response(['kind3_status' => 'N/A (PHP admin)', 'whitelist_count' => $wl_count, 'level_description' => $descriptions[$level] ?? $descriptions['0']]);
}
// GET: auth rules
$rules = [];
try { $rules = $pdo->query("SELECT id, rule_type, pattern_type, pattern_value FROM auth_rules ORDER BY id")->fetchAll(); } catch (PDOException $e) {}
json_response(['rules' => $rules]);
+153
View File
@@ -0,0 +1,153 @@
<?php
/** admin2/api/caching.php — Caching service status + follows + reset actions. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// --- POST actions: reset backfill (all or per-user) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$action = $input['action'] ?? '';
$pubkey = $input['pubkey'] ?? '';
if ($action === 'reset_all') {
// Reset backfill progress for ALL followed authors, then bump config
// generation so the running caching service hot-reloads and re-drains.
try {
$pdo->beginTransaction();
$pdo->exec("UPDATE caching_followed_pubkeys
SET until_cursor = 0, backfill_complete = FALSE,
events_fetched = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT");
// Also reset per-relay progress rows
$pdo->exec("UPDATE caching_backfill_relay_progress
SET complete = FALSE, events_fetched = 0,
until_cursor = 0, last_status = 'reset',
consecutive_errors = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT");
// Bump config generation to trigger hot-reload
$pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text
WHERE key = 'caching_config_generation'");
$pdo->commit();
json_response(['ok' => true, 'message' => 'All backfill progress reset. Caching service will re-drain.']);
} catch (PDOException $e) {
$pdo->rollBack();
json_response(['ok' => false, 'error' => $e->getMessage()], 500);
}
} elseif ($action === 'reset_user' && $pubkey) {
// Reset backfill progress for a single followed author.
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE caching_followed_pubkeys
SET until_cursor = 0, backfill_complete = FALSE,
events_fetched = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT
WHERE pubkey = ?");
$stmt->execute([$pubkey]);
// Also reset per-relay progress for this author
$stmt2 = $pdo->prepare("UPDATE caching_backfill_relay_progress
SET complete = FALSE, events_fetched = 0,
until_cursor = 0, last_status = 'reset',
consecutive_errors = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT
WHERE author_pubkey = ?");
$stmt2->execute([$pubkey]);
// Bump config generation to trigger hot-reload
$pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text
WHERE key = 'caching_config_generation'");
$pdo->commit();
json_response(['ok' => true, 'message' => "Backfill reset for pubkey. Caching service will re-fetch."]);
} catch (PDOException $e) {
$pdo->rollBack();
json_response(['ok' => false, 'error' => $e->getMessage()], 500);
}
} else {
json_response(['ok' => false, 'error' => 'Unknown action'], 400);
}
exit;
}
// Caching config values (for toggle controls)
$config = [];
try {
$cfg_rows = $pdo->query("SELECT key, value FROM config WHERE key IN ('caching_enabled','caching_inbox_enabled','caching_live_enabled','caching_backfill_enabled')")->fetchAll();
foreach ($cfg_rows as $r) { $config[$r['key']] = $r['value']; }
} catch (PDOException $e) {}
// Service state
$state = [];
try { $state = $pdo->query("SELECT * FROM caching_service_state WHERE id = 1")->fetch() ?: []; } catch (PDOException $e) {}
// Active target
$active = ['pubkey' => '', 'relay' => ''];
try { $active = $pdo->query("SELECT active_pubkey AS pubkey, active_relay AS relay FROM caching_backfill_active WHERE id = 1")->fetch() ?: $active; } catch (PDOException $e) {}
// Inbox
$inbox = ['pending' => 0, 'live' => 0, 'backfill' => 0];
try {
$inbox = $pdo->query("SELECT COUNT(*) AS pending, COUNT(*) FILTER (WHERE source_class='live') AS live, COUNT(*) FILTER (WHERE source_class='backfill') AS backfill FROM caching_event_inbox")->fetch() ?: $inbox;
} catch (PDOException $e) {}
// Upstream relay status (per-relay connection state)
$upstreamRelays = [];
try {
$upstreamRelays = $pdo->query("
SELECT relay_url, status_code, status_text, updated_at
FROM caching_upstream_relays
ORDER BY relay_url
")->fetchAll();
} catch (PDOException $e) {}
// Follows (paginated, with names from profiles cache + per-relay progress)
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 50;
$offset = ($page - 1) * $perPage;
$follows = [];
$totalFollows = 0;
try {
$totalFollows = (int)$pdo->query("SELECT COUNT(*) FROM caching_followed_pubkeys")->fetchColumn();
} catch (PDOException $e) {}
try {
$follows = $pdo->query("
SELECT fp.pubkey, fp.is_root, fp.backfill_complete, fp.events_fetched,
fp.last_event_at,
p.name, p.display_name,
(SELECT count(*) FROM events WHERE pubkey = fp.pubkey) AS total_events,
(SELECT count(*) FROM caching_backfill_relay_progress WHERE author_pubkey = fp.pubkey) AS relay_count,
(SELECT count(*) FROM caching_backfill_relay_progress WHERE author_pubkey = fp.pubkey AND complete = false) AS relay_incomplete
FROM caching_followed_pubkeys fp
LEFT JOIN profiles p ON p.pubkey = fp.pubkey
ORDER BY fp.is_root DESC, fp.events_fetched DESC
LIMIT $perPage OFFSET $offset
")->fetchAll();
// Fetch per-relay progress for all followed pubkeys in one query
$relayProgress = [];
try {
$rpRows = $pdo->query("
SELECT author_pubkey, relay_url, complete, events_fetched, until_cursor, last_status, updated_at
FROM caching_backfill_relay_progress
ORDER BY author_pubkey, events_fetched DESC
")->fetchAll();
foreach ($rpRows as $rp) {
$relayProgress[$rp['author_pubkey']][] = $rp;
}
} catch (PDOException $e) {}
foreach ($follows as &$f) {
$f['name'] = profile_display_name($f);
$f['npub'] = function_exists('hex_to_npub') ? hex_to_npub($f['pubkey']) : substr($f['pubkey'], 0, 20);
$f['relays'] = $relayProgress[$f['pubkey']] ?? [];
}
} catch (PDOException $e) {}
json_response([
'state' => $state,
'active' => $active,
'inbox' => $inbox,
'follows' => $follows,
'config' => $config,
'upstreamRelays' => $upstreamRelays,
'totalFollows' => $totalFollows,
'page' => $page,
'perPage' => $perPage,
]);
+137
View File
@@ -0,0 +1,137 @@
<?php
/**
* admin/api/chart.php — Standalone ASCII chart endpoint.
*
* Returns a plain-text ASCII X-bar chart of event counts over time.
* Works in both the browser (injected into a <div>) and the terminal:
*
* curl http://localhost:8088/api/chart.php?range=hour
* curl http://localhost:8088/api/chart.php?range=day
* curl http://localhost:8088/api/chart.php?range=month
* curl http://localhost:8088/api/chart.php?range=year
*
* This endpoint is read-only (a fixed aggregate COUNT query with the only
* user input being the `range` selector, which is validated against a
* whitelist). It is safe to expose publicly and is served without auth at:
*
* https://<domain>/relay/api/chart.php?range=day
*
* It is also reachable (behind Basic Auth) from the admin UI at:
*
* https://<domain>/relay/admin/api/chart.php?range=day
*
* Caching: the hour chart is never cached (live). Day/month/year are
* cached to file with TTLs to avoid expensive queries on every request.
*
* NOTE: Uses first_seen (not created_at) for binning, because some events
* have corrupted created_at timestamps (far-future values) that would
* place them outside the visible chart range.
*/
require_once __DIR__ . '/../lib/helpers.php';
require_once __DIR__ . '/../lib/ascii_chart.php';
// --- Range configuration ---
$ranges = [
'hour' => ['span' => 3600, 'bin' => 45, 'bins' => 80, 'ttl' => 0, 'title' => 'New Events — Last Hour'],
'day' => ['span' => 86400, 'bin' => 1080, 'bins' => 80, 'ttl' => 3600, 'title' => 'New Events — Last 24 Hours'],
'month' => ['span' => 2592000, 'bin' => 32400, 'bins' => 80, 'ttl' => 86400, 'title' => 'New Events — Last 30 Days'],
'year' => ['span' => 31536000, 'bin' => 394200, 'bins' => 80, 'ttl' => 2592000, 'title' => 'New Events — Last Year'],
];
$range = $_GET['range'] ?? 'hour';
if (!isset($ranges[$range])) {
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo "Invalid range. Use: hour, day, month, or year.\n";
exit;
}
$cfg = $ranges[$range];
// --- Set content type ---
header('Content-Type: text/plain; charset=utf-8');
// --- Check cache ---
if ($cfg['ttl'] > 0) {
$cached = get_cached_chart($range, $cfg['ttl']);
if ($cached !== null) {
echo $cached;
exit;
}
}
// --- Run binning query ---
$pdo = db();
$now = time();
$epoch = $now - $cfg['span'];
$bin_size = $cfg['bin'];
$num_bins = $cfg['bins'];
try {
// Calculate the bin offset so the rightmost bin aligns with "now"
// bin index 0 = oldest, bin index (num_bins-1) = newest
$base_bin = (int)floor($epoch / $bin_size);
// Use first_seen (not created_at) for binning, because some events
// have corrupted created_at timestamps (far-future values) that
// would place them outside the visible chart range.
$sql = "SELECT FLOOR(first_seen / {$bin_size})::BIGINT - {$base_bin} AS bin, COUNT(*) AS cnt
FROM events
WHERE first_seen >= {$epoch}
GROUP BY bin
ORDER BY bin";
$rows = $pdo->query($sql)->fetchAll();
} catch (PDOException $e) {
echo "Chart query failed: " . $e->getMessage() . "\n";
exit;
}
// --- Build bin array (zero-filled, fixed length) ---
// Index 0 = oldest, index N-1 = newest (natural time order from SQL)
$bins = build_bin_array($rows, $num_bins);
// Reverse so newest bin is at index 0 (leftmost) and oldest at right.
// This matches the original text_graph.js display: new data enters
// from the left and proceeds rightward as it ages.
$bins = array_reverse($bins);
// --- Render ASCII chart ---
$ascii = render_ascii_chart($bins, [
'title' => $cfg['title'],
'max_height' => 11,
'bin_duration' => $bin_size,
'label_interval' => max(1, (int)floor($num_bins / 15)), // ~15 labels across
]);
// --- Write to cache (if TTL > 0) ---
if ($cfg['ttl'] > 0) {
set_cached_chart($range, $ascii);
}
echo $ascii;
// ================================
// CACHE HELPERS
// ================================
function get_cache_dir(): string {
return __DIR__ . '/../cache';
}
function get_cached_chart(string $range, int $ttl): ?string {
$file = get_cache_dir() . "/chart_{$range}.txt";
if (!file_exists($file)) return null;
if (filemtime($file) < (time() - $ttl)) return null;
$content = @file_get_contents($file);
return ($content !== false) ? $content : null;
}
function set_cached_chart(string $range, string $ascii): void {
$dir = get_cache_dir();
if (!is_dir($dir)) {
@mkdir($dir, 0775, true);
}
@file_put_contents($dir . "/chart_{$range}.txt", $ascii);
}
+286
View File
@@ -0,0 +1,286 @@
<?php
/**
* admin2/api/cleanup.php — Event Cleanup: Preview & Execute.
*
* GET — Preview a cleanup query (count + size estimate, no delete).
* POST — Execute a cleanup query (dry_run=true for preview, false for delete).
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
// ── Shared filter builder ──────────────────────────────────────────────
// Returns [sql_conditions[], params[]] for the WHERE clauses.
function build_filters(array $opts): array {
$conds = [];
$params = [];
// Kinds filter
if (!empty($opts['kinds'])) {
$kinds = is_array($opts['kinds']) ? $opts['kinds'] : explode(',', $opts['kinds']);
$kinds = array_map('intval', $kinds);
$kinds = array_filter($kinds, fn($v) => $v > 0);
if (!empty($kinds)) {
$placeholders = [];
foreach ($kinds as $i => $k) {
$key = ':kind_' . $i;
$placeholders[] = $key;
$params[$key] = $k;
}
$conds[] = 'e.kind IN (' . implode(',', $placeholders) . ')';
}
}
// Max age filter
$max_age_days = intval($opts['max_age_days'] ?? 0);
if ($max_age_days > 0) {
$conds[] = 'e.created_at < EXTRACT(EPOCH FROM NOW())::BIGINT - :max_age_seconds';
$params[':max_age_seconds'] = $max_age_days * 86400;
}
// Follows filter
$follows_filter = $opts['follows_filter'] ?? 'all';
if ($follows_filter === 'follows') {
$conds[] = 'e.pubkey IN (SELECT pubkey FROM caching_followed_pubkeys)';
} elseif ($follows_filter === 'non_follows') {
$conds[] = 'e.pubkey NOT IN (SELECT pubkey FROM caching_followed_pubkeys)';
}
return [$conds, $params];
}
// ── Build a WHERE clause string from conditions ────────────────────────
function where_clause(array $conds): string {
if (empty($conds)) return '';
return 'AND ' . implode("\n AND ", $conds);
}
// ── Format bytes as human-readable ─────────────────────────────────────
function format_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
// ── GET: Preview ───────────────────────────────────────────────────────
if ($method === 'GET') {
$opts = [
'follows_filter' => $_GET['follows_filter'] ?? 'all',
'kinds' => $_GET['kinds'] ?? '',
'max_age_days' => intval($_GET['max_age_days'] ?? 0),
'max_events' => intval($_GET['max_events'] ?? 0),
];
list($conds, $params) = build_filters($opts);
$where = where_clause($conds);
// Preview SQL (for display)
$sql_preview = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1\n"
. ($where ? " $where\n" : '');
// Count + size query
$count_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($count_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$match_count = intval($row['match_count'] ?? 0);
$total_size_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Preview query failed: ' . $e->getMessage()]);
exit;
}
// Kind breakdown
$breakdown = [];
if ($match_count > 0) {
$breakdown_sql = "SELECT e.kind,\n"
. " COUNT(*) AS count,\n"
. " COALESCE(SUM(pg_column_size(e.event_json)), 0) AS size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where\n"
. "GROUP BY e.kind\n"
. "ORDER BY count DESC\n"
. "LIMIT 50";
try {
$stmt = $pdo->prepare($breakdown_sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
foreach ($rows as $r) {
$breakdown[] = [
'kind' => intval($r['kind']),
'count' => intval($r['count']),
'size_bytes' => intval($r['size_bytes']),
];
}
} catch (PDOException $e) {
// Breakdown is non-critical
}
}
// If a query_id was provided, update last_preview_count and last_preview_size_bytes
$query_id = intval($_GET['query_id'] ?? 0);
if ($query_id > 0) {
try {
$pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET last_preview_count = :count,\n"
. " last_preview_size_bytes = :size\n"
. " WHERE id = :id"
)->execute([
':id' => $query_id,
':count' => $match_count,
':size' => $total_size_bytes,
]);
} catch (PDOException $e) {}
}
json_response([
'match_count' => $match_count,
'total_size_bytes' => $total_size_bytes,
'total_size_human' => format_bytes($total_size_bytes),
'avg_size_per_event' => $match_count > 0 ? intval($total_size_bytes / $match_count) : 0,
'kinds_breakdown' => $breakdown,
'sql_preview' => $sql_preview,
]);
exit;
}
// ── POST: Execute (or dry-run) ─────────────────────────────────────────
if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body) {
json_response(['error' => 'Invalid JSON body']);
exit;
}
$dry_run = !empty($body['dry_run']);
$opts = [
'follows_filter' => $body['follows_filter'] ?? 'all',
'kinds' => $body['kinds'] ?? [],
'max_age_days' => intval($body['max_age_days'] ?? 0),
'max_events' => intval($body['max_events'] ?? 0),
];
list($conds, $params) = build_filters($opts);
$where = where_clause($conds);
// If dry_run, return preview (same as GET)
if ($dry_run) {
$count_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($count_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$match_count = intval($row['match_count'] ?? 0);
$total_size_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Preview query failed: ' . $e->getMessage()]);
exit;
}
// Kind breakdown
$breakdown = [];
if ($match_count > 0) {
$breakdown_sql = "SELECT e.kind, COUNT(*) AS count,\n"
. " COALESCE(SUM(pg_column_size(e.event_json)), 0) AS size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where\n"
. "GROUP BY e.kind\n"
. "ORDER BY count DESC\n"
. "LIMIT 50";
try {
$stmt = $pdo->prepare($breakdown_sql);
$stmt->execute($params);
foreach ($stmt->fetchAll() as $r) {
$breakdown[] = [
'kind' => intval($r['kind']),
'count' => intval($r['count']),
'size_bytes' => intval($r['size_bytes']),
];
}
} catch (PDOException $e) {}
}
json_response([
'match_count' => $match_count,
'total_size_bytes' => $total_size_bytes,
'total_size_human' => format_bytes($total_size_bytes),
'avg_size_per_event' => $match_count > 0 ? intval($total_size_bytes / $match_count) : 0,
'kinds_breakdown' => $breakdown,
'dry_run' => true,
]);
exit;
}
// ── Actual DELETE ──────────────────────────────────────────────────
$max_events = intval($body['max_events'] ?? 0);
$limit_clause = $max_events > 0 ? 'LIMIT :max_events' : '';
// First, get the count and size of what will be deleted
$preview_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($preview_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$expected_count = intval($row['match_count'] ?? 0);
$expected_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Pre-delete count failed: ' . $e->getMessage()]);
exit;
}
// Build the DELETE using id IN (subquery) for safe LIMIT + ORDER BY
$delete_sql = "DELETE FROM events e\n"
. "WHERE e.id IN (\n"
. " SELECT e2.id FROM events e2\n"
. " WHERE 1=1 $where\n"
. " ORDER BY e2.created_at ASC\n"
. " $limit_clause\n"
. ")";
$start_time = microtime(true);
try {
$stmt = $pdo->prepare($delete_sql);
if ($max_events > 0) {
$params[':max_events'] = $max_events;
}
$stmt->execute($params);
$deleted_count = $stmt->rowCount();
} catch (PDOException $e) {
json_response(['error' => 'Delete query failed: ' . $e->getMessage()]);
exit;
}
$duration_ms = round((microtime(true) - $start_time) * 1000);
// If we deleted fewer than expected, the actual freed bytes are proportional
$freed_bytes = $expected_count > 0
? intval($expected_bytes * ($deleted_count / $expected_count))
: 0;
json_response([
'deleted_count' => $deleted_count,
'freed_bytes' => $freed_bytes,
'freed_human' => format_bytes($freed_bytes),
'duration_ms' => $duration_ms,
]);
exit;
}
// Unsupported method
json_response(['error' => 'Method not allowed']);
+317
View File
@@ -0,0 +1,317 @@
<?php
/**
* admin2/api/cleanup_queries.php — Saved Cleanup Query CRUD.
*
* GET — List all saved queries.
* POST — Create/update/delete/execute saved queries.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
/** Format bytes as human-readable. */
function fmt_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
// ── GET: List all saved queries ────────────────────────────────────────
if ($method === 'GET') {
try {
$rows = $pdo->query(
"SELECT id, name, follows_filter, kinds, max_age_days, max_events,\n"
. " last_preview_count, last_preview_size_bytes,\n"
. " last_executed_at, created_at, updated_at\n"
. " FROM cleanup_saved_queries\n"
. " ORDER BY updated_at DESC"
)->fetchAll();
} catch (PDOException $e) {
// Table may not exist yet
json_response(['queries' => []]);
exit;
}
$queries = [];
foreach ($rows as $r) {
$kinds = $r['kinds'];
if (is_string($kinds)) {
// PostgreSQL returns {1,7} format — parse it
$kinds = trim($kinds, '{}');
$kinds = $kinds !== '' ? array_map('intval', explode(',', $kinds)) : [];
} elseif (is_resource($kinds)) {
$kinds = [];
}
$queries[] = [
'id' => intval($r['id']),
'name' => $r['name'],
'follows_filter' => $r['follows_filter'],
'kinds' => $kinds,
'max_age_days' => intval($r['max_age_days']),
'max_events' => intval($r['max_events']),
'last_preview_count' => intval($r['last_preview_count']),
'last_preview_size_human' => fmt_bytes(intval($r['last_preview_size_bytes'])),
'last_executed_at' => intval($r['last_executed_at']) > 0
? date('Y-m-d H:i:s', intval($r['last_executed_at']))
: null,
'created_at' => date('Y-m-d H:i:s', intval($r['created_at'])),
'updated_at' => date('Y-m-d H:i:s', intval($r['updated_at'])),
];
}
json_response(['queries' => $queries]);
exit;
}
// ── POST: Actions ──────────────────────────────────────────────────────
if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body || empty($body['action'])) {
json_response(['error' => 'Missing action']);
exit;
}
$action = $body['action'];
// ── Save (create or update) ────────────────────────────────────────
if ($action === 'save') {
$id = $body['id'] ?? null;
$name = trim($body['name'] ?? '');
if ($name === '') {
json_response(['error' => 'Name is required']);
exit;
}
$follows_filter = $body['follows_filter'] ?? 'all';
if (!in_array($follows_filter, ['all', 'follows', 'non_follows'])) {
$follows_filter = 'all';
}
$kinds = $body['kinds'] ?? [];
if (is_array($kinds)) {
$kinds = array_map('intval', $kinds);
$kinds = array_filter($kinds, fn($v) => $v > 0);
$kinds = array_values($kinds);
} else {
$kinds = [];
}
// PostgreSQL array literal
$kinds_pg = '{' . implode(',', $kinds) . '}';
$max_age_days = intval($body['max_age_days'] ?? 0);
$max_events = intval($body['max_events'] ?? 0);
try {
if ($id) {
// Update existing
$stmt = $pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET name = :name,\n"
. " follows_filter = :follows_filter,\n"
. " kinds = :kinds::integer[],\n"
. " max_age_days = :max_age_days,\n"
. " max_events = :max_events,\n"
. " updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT\n"
. " WHERE id = :id"
);
$stmt->execute([
':id' => $id,
':name' => $name,
':follows_filter' => $follows_filter,
':kinds' => $kinds_pg,
':max_age_days' => $max_age_days,
':max_events' => $max_events,
]);
} else {
// Insert new
$stmt = $pdo->prepare(
"INSERT INTO cleanup_saved_queries\n"
. " (name, follows_filter, kinds, max_age_days, max_events)\n"
. "VALUES (:name, :follows_filter, :kinds::integer[],\n"
. " :max_age_days, :max_events)\n"
. "ON CONFLICT (name) DO UPDATE SET\n"
. " follows_filter = EXCLUDED.follows_filter,\n"
. " kinds = EXCLUDED.kinds,\n"
. " max_age_days = EXCLUDED.max_age_days,\n"
. " max_events = EXCLUDED.max_events,\n"
. " updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"
);
$stmt->execute([
':name' => $name,
':follows_filter' => $follows_filter,
':kinds' => $kinds_pg,
':max_age_days' => $max_age_days,
':max_events' => $max_events,
]);
$id = $pdo->lastInsertId();
}
} catch (PDOException $e) {
json_response(['error' => 'Save failed: ' . $e->getMessage()]);
exit;
}
json_response(['ok' => true, 'id' => intval($id)]);
exit;
}
// ── Delete ─────────────────────────────────────────────────────────
if ($action === 'delete') {
$id = intval($body['id'] ?? 0);
if ($id <= 0) {
json_response(['error' => 'Invalid id']);
exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM cleanup_saved_queries WHERE id = :id");
$stmt->execute([':id' => $id]);
} catch (PDOException $e) {
json_response(['error' => 'Delete failed: ' . $e->getMessage()]);
exit;
}
json_response(['ok' => true]);
exit;
}
// ── Execute (run a saved query as non-dry-run DELETE) ──────────────
if ($action === 'execute') {
$id = intval($body['id'] ?? 0);
if ($id <= 0) {
json_response(['error' => 'Invalid id']);
exit;
}
// Load the saved query
try {
$stmt = $pdo->prepare(
"SELECT id, name, follows_filter, kinds, max_age_days, max_events\n"
. " FROM cleanup_saved_queries\n"
. " WHERE id = :id"
);
$stmt->execute([':id' => $id]);
$query = $stmt->fetch();
} catch (PDOException $e) {
json_response(['error' => 'Query load failed: ' . $e->getMessage()]);
exit;
}
if (!$query) {
json_response(['error' => 'Saved query not found']);
exit;
}
// Parse kinds from PG array
$kinds_raw = $query['kinds'];
if (is_string($kinds_raw)) {
$kinds_raw = trim($kinds_raw, '{}');
$kinds = $kinds_raw !== '' ? array_map('intval', explode(',', $kinds_raw)) : [];
} else {
$kinds = [];
}
// Build filters (same logic as cleanup.php)
$conds = [];
$params = [];
if (!empty($kinds)) {
$kind_placeholders = [];
foreach ($kinds as $i => $k) {
$key = ':kind_' . $i;
$kind_placeholders[] = $key;
$params[$key] = $k;
}
$conds[] = 'e.kind IN (' . implode(',', $kind_placeholders) . ')';
}
$max_age_days = intval($query['max_age_days']);
if ($max_age_days > 0) {
$conds[] = 'e.created_at < EXTRACT(EPOCH FROM NOW())::BIGINT - :max_age_seconds';
$params[':max_age_seconds'] = $max_age_days * 86400;
}
$follows_filter = $query['follows_filter'];
if ($follows_filter === 'follows') {
$conds[] = 'e.pubkey IN (SELECT pubkey FROM caching_followed_pubkeys)';
} elseif ($follows_filter === 'non_follows') {
$conds[] = 'e.pubkey NOT IN (SELECT pubkey FROM caching_followed_pubkeys)';
}
$where = !empty($conds) ? 'AND ' . implode("\n AND ", $conds) : '';
$max_events = intval($query['max_events']);
$limit_clause = $max_events > 0 ? 'LIMIT :max_events' : '';
// Pre-delete count
$preview_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($preview_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$expected_count = intval($row['match_count'] ?? 0);
$expected_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Pre-delete count failed: ' . $e->getMessage()]);
exit;
}
// Execute DELETE
$delete_sql = "DELETE FROM events e\n"
. "WHERE e.id IN (\n"
. " SELECT e2.id FROM events e2\n"
. " WHERE 1=1 $where\n"
. " ORDER BY e2.created_at ASC\n"
. " $limit_clause\n"
. ")";
$start_time = microtime(true);
try {
$stmt = $pdo->prepare($delete_sql);
if ($max_events > 0) {
$params[':max_events'] = $max_events;
}
$stmt->execute($params);
$deleted_count = $stmt->rowCount();
} catch (PDOException $e) {
json_response(['error' => 'Delete failed: ' . $e->getMessage()]);
exit;
}
$duration_ms = round((microtime(true) - $start_time) * 1000);
$freed_bytes = $expected_count > 0
? intval($expected_bytes * ($deleted_count / $expected_count))
: 0;
// Update last_preview_count, last_preview_size_bytes, last_executed_at
try {
$pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET last_preview_count = :count,\n"
. " last_preview_size_bytes = :size,\n"
. " last_executed_at = EXTRACT(EPOCH FROM NOW())::BIGINT\n"
. " WHERE id = :id"
)->execute([
':id' => $id,
':count' => $expected_count,
':size' => $expected_bytes,
]);
} catch (PDOException $e) {}
json_response([
'deleted_count' => $deleted_count,
'freed_bytes' => $freed_bytes,
'freed_human' => fmt_bytes($freed_bytes),
'duration_ms' => $duration_ms,
]);
exit;
}
json_response(['error' => 'Unknown action: ' . $action]);
exit;
}
json_response(['error' => 'Method not allowed']);
+20
View File
@@ -0,0 +1,20 @@
<?php
/** admin2/api/config.php — Config table read/edit (all keys). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$key = $input['key'] ?? '';
$value = $input['value'] ?? '';
if (!$key) json_response(['error' => 'Missing key']);
try {
$pdo->prepare("UPDATE config SET value = ? WHERE key = ?")->execute([$value, $key]);
json_response(['message' => "Updated $key"]);
} catch (PDOException $e) {
json_response(['error' => $e->getMessage()]);
}
}
$rows = $pdo->query("SELECT key, value FROM config ORDER BY key")->fetchAll();
json_response(['config' => $rows]);
+16
View File
@@ -0,0 +1,16 @@
<?php
/** admin2/api/dm.php — Direct messages (kind 4/14/15). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$limit = min(200, intval($_GET['limit'] ?? 50));
$rows = $pdo->prepare("SELECT id, pubkey, kind, created_at, content FROM events WHERE kind IN (4, 14, 15) ORDER BY created_at DESC LIMIT $limit");
$rows->execute();
$messages = $rows->fetchAll();
foreach ($messages as &$m) {
$m['created_at'] = date('Y-m-d H:i:s', intval($m['created_at']));
$m['content'] = substr($m['content'] ?? '', 0, 300);
}
json_response(['messages' => $messages]);
+36
View File
@@ -0,0 +1,36 @@
<?php
/** admin2/api/events.php — Recent relay events (paginated). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$limit = min(200, intval($_GET['limit'] ?? 50));
$offset = intval($_GET['offset'] ?? 0);
$kind = $_GET['kind'] ?? '';
$where = [];
$params = [];
if ($kind !== '') { $where[] = 'kind = ?'; $params[] = intval($kind); }
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
// Order by first_seen DESC to show most recently received events first,
// avoiding corrupted created_at timestamps (e.g. far-future values) that
// would otherwise dominate the ordering.
$rows = $pdo->prepare("SELECT e.id, e.pubkey, e.kind, e.created_at, e.first_seen, e.content,
p.name, p.display_name
FROM events e
LEFT JOIN profiles p ON p.pubkey = e.pubkey
$where_sql
ORDER BY e.first_seen DESC LIMIT $limit OFFSET $offset");
$rows->execute($params);
$events = $rows->fetchAll();
foreach ($events as &$e) {
$e['created_at'] = date('Y-m-d H:i:s', intval($e['created_at']));
$e['first_seen'] = date('Y-m-d H:i:s', intval($e['first_seen']));
$e['content'] = substr($e['content'] ?? '', 0, 200);
// Resolve display name from profiles cache; fall back to truncated pubkey.
$best = profile_display_name($e);
$e['display_name'] = $best !== '' ? $best : substr($e['pubkey'] ?? '', 0, 16) . '…';
}
json_response(['events' => $events]);
+35
View File
@@ -0,0 +1,35 @@
<?php
/** admin2/api/ipbans.php — IP ban management. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
try {
if ($action === 'add') {
$until = time() + intval($input['duration'] ?? 86400);
$pdo->prepare("INSERT INTO ip_bans (ip, banned_until, ban_count) VALUES (?, ?, 1) ON CONFLICT (ip) DO UPDATE SET banned_until = ?, ban_count = ip_bans.ban_count + 1")->execute([$input['ip'], $until, $until]);
json_response(['message' => 'IP banned']);
} elseif ($action === 'remove') {
$pdo->prepare("DELETE FROM ip_bans WHERE ip = ?")->execute([$input['ip']]);
json_response(['message' => 'Ban removed']);
}
} catch (PDOException $e) { json_response(['error' => $e->getMessage()]); }
}
$now = time();
$total = 0; $active = 0; $issued = 0;
$bans = [];
try {
$total = intval($pdo->query("SELECT count(*) FROM ip_bans")->fetchColumn());
$active = intval($pdo->query("SELECT count(*) FROM ip_bans WHERE banned_until > $now")->fetchColumn());
$issued = intval($pdo->query("SELECT COALESCE(sum(ban_count),0) FROM ip_bans")->fetchColumn());
$bans = $pdo->query("SELECT ip, banned_until, ban_count, failures, authed_successfully, connection_attempts FROM ip_bans ORDER BY banned_until DESC LIMIT 100")->fetchAll();
foreach ($bans as &$b) {
$b['status'] = intval($b['banned_until']) > $now ? 'banned' : 'expired';
$b['banned_until'] = date('Y-m-d H:i:s', intval($b['banned_until']));
}
} catch (PDOException $e) {}
json_response(['total' => $total, 'active' => $active, 'issued' => $issued, 'bans' => $bans]);
+238
View File
@@ -0,0 +1,238 @@
<?php
/**
* admin/api/kind_1_report.php — Markdown relay status report.
*
* Returns a markdown-formatted relay status report suitable for use as
* a kind 1 event content. Includes:
* - Relay name header
* - 1H ASCII chart (event rate)
* - Database overview
* - Top 10 event kinds
* - Time-based statistics
* - Top pubkeys
*
* Accessible at: /relay/admin/api/kind_1_report.php
*/
require_once __DIR__ . '/../lib/helpers.php';
require_once __DIR__ . '/../lib/ascii_chart.php';
header('Content-Type: text/plain; charset=utf-8');
$pdo = db();
// --- Relay info ---
$relay_name = 'C-Relay-PG';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
$display_name = $relay_version ? "$relay_name v$relay_version" : $relay_name;
// --- Total events ---
$total_events = 0;
try {
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
} catch (PDOException $e) {}
// --- Database size ---
$db_size = '-';
try {
$bytes = intval($pdo->query("SELECT pg_database_size(current_database())")->fetchColumn());
if ($bytes < 1073741824) {
$db_size = round($bytes / 1048576, 1) . ' MB';
} else {
$db_size = round($bytes / 1073741824, 2) . ' GB';
}
} catch (PDOException $e) {}
// --- Process ID ---
$process_id = '-';
try {
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
$process_id = strval($pid);
} catch (PDOException $e) {}
// --- WebSocket connections (estimate from pg_class) ---
$ws_connections = 0;
try {
$est = intval($pdo->query("SELECT reltuples FROM pg_class WHERE relname = 'subscriptions'")->fetchColumn());
$ws_connections = max(0, intval($est * 0.2));
} catch (PDOException $e) {}
// --- Active subscriptions (same estimate) ---
$active_subscriptions = $ws_connections;
// --- Memory usage ---
$memory_usage = '-';
$mem_total = 0;
$mem_avail = 0;
$meminfo = @file_get_contents('/proc/meminfo');
if ($meminfo !== false) {
// /proc/meminfo uses "Key: Value kB" format (colon, not equals sign)
foreach (explode("\n", $meminfo) as $line) {
if (preg_match('/^MemTotal:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_total = intval($m[1]) * 1024;
} elseif (preg_match('/^MemAvailable:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_avail = intval($m[1]) * 1024;
}
}
}
if ($mem_total > 0) {
$used = $mem_total - $mem_avail;
if ($used < 1073741824) {
$memory_usage = round($used / 1048576, 1) . ' MB / ' . round($mem_total / 1073741824, 2) . ' GB';
} else {
$memory_usage = round($used / 1073741824, 2) . ' GB / ' . round($mem_total / 1073741824, 2) . ' GB';
}
}
// --- CPU ---
$cpu_usage = '-';
$cpu_core = '-';
if (is_readable('/proc/cpuinfo')) {
$cores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);
$cpu_core = strval($cores) . ' cores';
}
$load = @file_get_contents('/proc/loadavg');
if ($load !== false) {
$cpu_usage = trim(explode(' ', $load)[0] ?? '-');
}
// --- Oldest / newest event ---
$oldest_event = '-';
$newest_event = '-';
try {
$row = $pdo->query("SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest FROM events")->fetch();
if ($row && $row['oldest']) $oldest_event = date('Y-m-d H:i:s', intval($row['oldest']));
if ($row && $row['newest']) $newest_event = date('Y-m-d H:i:s', intval($row['newest']));
} catch (PDOException $e) {}
// --- Time-based stats ---
$now = time();
$events_24h = 0; $events_7d = 0; $events_30d = 0;
try {
$events_24h = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 86400")->fetchColumn());
$events_7d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 604800")->fetchColumn());
$events_30d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 2592000")->fetchColumn());
} catch (PDOException $e) {}
// --- Kind distribution (top 10) ---
$kinds = [];
try {
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 10")->fetchAll();
foreach ($rows as $r) {
$kinds[] = ['kind' => intval($r['kind']), 'count' => intval($r['cnt']), 'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0];
}
} catch (PDOException $e) {}
// --- Top pubkeys (top 20) ---
$top_pubkeys = [];
try {
$rows = $pdo->query("
SELECT pubkey, COUNT(*) AS cnt
FROM events
GROUP BY pubkey
ORDER BY cnt DESC LIMIT 20
")->fetchAll();
$pubkeys = array_column($rows, 'pubkey');
$pmap = profile_map($pubkeys);
foreach ($rows as $r) {
$pk = $r['pubkey'];
$prof = $pmap[$pk] ?? null;
$top_pubkeys[] = [
'pubkey' => $pk,
'name' => $prof ? $prof['best_name'] : '',
'count' => intval($r['cnt']),
'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0,
];
}
} catch (PDOException $e) {}
// --- 1H ASCII chart (30 bins of 2 minutes each) ---
$chart = '';
try {
$span = 3600;
$bin_size = 120; // 2 minutes
$num_bins = 30;
$epoch = $now - $span;
$base_bin = (int)floor($epoch / $bin_size);
$sql = "SELECT FLOOR(created_at / {$bin_size})::BIGINT - {$base_bin} AS bin, COUNT(*) AS cnt
FROM events
WHERE created_at >= {$epoch}
GROUP BY bin
ORDER BY bin";
$rows = $pdo->query($sql)->fetchAll();
$bins = build_bin_array($rows, $num_bins);
$bins = array_reverse($bins);
$chart = render_ascii_chart($bins, [
'title' => 'Event Rate (Last Hour)',
'max_height' => 11,
'bin_duration' => $bin_size,
'label_interval' => 5, // label every 5 bins (10 min)
]);
} catch (PDOException $e) {
$chart = "Chart unavailable\n";
}
// ================================
// BUILD MARKDOWN OUTPUT
// ================================
$output = '';
// Chart
$output .= "## laantungir.net/relay\n\n";
$output .= "```\n$chart```\n";
// Database Overview
$output .= "## Database Overview\n\n";
$output .= "| Metric | Value |\n";
$output .= "|--------|-------|\n";
$output .= "| Database Size | $db_size |\n";
$output .= "| Total Events | " . number_format($total_events) . " |\n";
$output .= "| Process ID | $process_id |\n";
$output .= "| WebSocket Connections | " . number_format($ws_connections) . " |\n";
$output .= "| Active Subscriptions | " . number_format($active_subscriptions) . " |\n";
$output .= "| Memory Usage | $memory_usage |\n";
$output .= "| CPU Usage | $cpu_usage |\n";
$output .= "| CPU Core | $cpu_core |\n";
$output .= "| Oldest Event | $oldest_event |\n";
$output .= "| Newest Event | $newest_event |\n\n";
// Event Kinds (Top 10)
$output .= "## Event Kinds (Top 10)\n\n";
$output .= "| Kind | Count | % |\n";
$output .= "|------|-------|---|\n";
foreach ($kinds as $k) {
$output .= "| {$k['kind']} | " . number_format($k['count']) . " | {$k['pct']}% |\n";
}
$output .= "\n";
// Time-Based Statistics
$output .= "## Time-Based Statistics\n\n";
$output .= "| Period | Events |\n";
$output .= "|--------|-------|\n";
$output .= "| 24 Hours | " . number_format($events_24h) . " |\n";
$output .= "| 7 Days | " . number_format($events_7d) . " |\n";
$output .= "| 30 Days | " . number_format($events_30d) . " |\n\n";
// Top Pubkeys
$output .= "## Top Pubkeys\n\n";
$output .= "| Name | Events | % |\n";
$output .= "|------|-------|---|\n";
foreach ($top_pubkeys as $p) {
if ($p['name']) {
$name = $p['name'];
} else {
$name = substr($p['pubkey'], 0, 4) . '..' . substr($p['pubkey'], -4);
}
$output .= "| $name | " . number_format($p['count']) . " | {$p['pct']}% |\n";
}
$output .= "\n";
// Footer
$output .= "---\n";
$output .= "_Report generated: " . date('Y-m-d H:i:s T') . "_\n";
echo $output;
+38
View File
@@ -0,0 +1,38 @@
<?php
/**
* admin/api/profile.php — Single-profile lookup from the profiles cache.
* Returns cached kind-0 metadata for a given pubkey.
*
* Usage: profile.php?pubkey=<64-char-hex>
* Returns: { "pubkey": "...", "name": "...", "display_name": "...",
* "best_name": "...", "picture": "...", "nip05": "...",
* "about": "...", "website": "...", "lud16": "..." }
* or { "error": "not found" } with HTTP 404 if no profile is cached.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pubkey = trim($_GET['pubkey'] ?? '');
if (!preg_match('/^[0-9a-fA-F]{64}$/', $pubkey)) {
http_response_code(400);
json_response(['error' => 'Invalid pubkey']);
}
$pdo = db();
try {
$stmt = $pdo->prepare(
"SELECT pubkey, name, display_name, about, picture, banner,
nip05, website, lud16, lud06
FROM profiles WHERE pubkey = ?"
);
$stmt->execute([$pubkey]);
$row = $stmt->fetch();
if (!$row) {
http_response_code(404);
json_response(['error' => 'not found']);
}
$row['best_name'] = profile_display_name($row);
json_response($row);
} catch (PDOException $e) {
http_response_code(500);
json_response(['error' => 'Database error']);
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/** admin2/api/query.php — Direct SQL query (SELECT only, admin only). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_response(['error' => 'POST only']);
}
$input = json_decode(file_get_contents('php://input'), true);
$sql = trim($input['sql'] ?? '');
if (!$sql) json_response(['error' => 'No query provided']);
if (!preg_match('/^\s*SELECT/i', $sql)) json_response(['error' => 'Only SELECT queries are allowed']);
$start = microtime(true);
try {
$stmt = $pdo->query($sql);
$rows = $stmt->fetchAll();
$time_ms = round((microtime(true) - $start) * 1000, 1);
json_response(['rows' => $rows, 'row_count' => count($rows), 'time_ms' => $time_ms]);
} catch (PDOException $e) {
json_response(['error' => $e->getMessage()]);
}
+188
View File
@@ -0,0 +1,188 @@
<?php
/**
* admin2/api/stats.php — Statistics page data.
* Returns DB size, event counts, kind distribution, top pubkeys,
* time-based stats, and event-rate delta (for the chart).
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// Database size
$db_size = '-';
try {
$bytes = $pdo->query("SELECT pg_database_size(current_database())")->fetchColumn();
$db_size = format_bytes(intval($bytes));
} catch (PDOException $e) {}
// Total events
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
// Event rate: events with first_seen in last 10 seconds
$events_delta = 0;
try {
$events_delta = intval($pdo->query("SELECT COUNT(*) FROM events WHERE first_seen >= EXTRACT(EPOCH FROM NOW())::BIGINT - 10")->fetchColumn());
} catch (PDOException $e) {}
// Process info
$process_id = '-';
try {
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
$process_id = strval($pid);
} catch (PDOException $e) {}
// WebSocket connections: tracked in-memory by the relay (g_connection_count).
// The subscriptions table is a 32M-row historical log, not a live state table.
// Use pg_class for an instant estimate (updated by autovacuum).
$ws_connections = 0;
try {
$est = intval($pdo->query("SELECT reltuples FROM pg_class WHERE relname = 'subscriptions'")->fetchColumn());
// ~60% are 'created', ~40% are 'closed' — active ≈ created - closed
$ws_connections = max(0, intval($est * 0.2));
} catch (PDOException $e) {}
// Active subscriptions: same estimate approach.
$active_subscriptions = $ws_connections;
// Memory/CPU (from /proc on Linux)
$memory_usage = '-';
$cpu_usage = '-';
$cpu_core = '-';
$mem_total = 0;
$mem_avail = 0;
$meminfo = @file_get_contents('/proc/meminfo');
if ($meminfo !== false) {
// /proc/meminfo uses "Key: Value kB" format (colon, not equals sign)
foreach (explode("\n", $meminfo) as $line) {
if (preg_match('/^MemTotal:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_total = intval($m[1]) * 1024;
} elseif (preg_match('/^MemAvailable:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_avail = intval($m[1]) * 1024;
}
}
}
if ($mem_total > 0 && $mem_avail > 0) {
$memory_usage = format_bytes($mem_total - $mem_avail) . ' / ' . format_bytes($mem_total);
} elseif ($mem_total > 0) {
$memory_usage = format_bytes($mem_total);
}
if (is_readable('/proc/cpuinfo')) {
$cores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);
$cpu_core = strval($cores) . ' cores';
}
$cpu_usage = @file_get_contents('/proc/loadavg');
if ($cpu_usage !== false) $cpu_usage = trim(explode(' ', $cpu_usage)[0] ?? '-');
// Oldest / newest event (single query)
$oldest_event = '-';
$newest_event = '-';
try {
$row = $pdo->query("SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest FROM events")->fetch();
if ($row && $row['oldest']) {
$oldest_event = date('Y-m-d H:i:s', intval($row['oldest']));
}
if ($row && $row['newest']) {
$newest_event = date('Y-m-d H:i:s', intval($row['newest']));
}
} catch (PDOException $e) {}
// Time-based stats
$now = time();
$events_24h = 0; $events_7d = 0; $events_30d = 0;
try {
$events_24h = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 86400")->fetchColumn());
$events_7d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 604800")->fetchColumn());
$events_30d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 2592000")->fetchColumn());
} catch (PDOException $e) {}
// Kind distribution — cached to file, refreshed every 5 minutes
$kinds = [];
$cache_dir = __DIR__ . '/../cache';
if (!is_dir($cache_dir)) @mkdir($cache_dir, 0755, true);
$kinds_cache = $cache_dir . '/stats_kinds.json';
$kinds_ttl = 300;
if (is_readable($kinds_cache) && (time() - filemtime($kinds_cache)) < $kinds_ttl) {
$kinds = json_decode(file_get_contents($kinds_cache), true) ?: [];
} else {
try {
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 20")->fetchAll();
foreach ($rows as $r) {
$kinds[] = ['kind' => intval($r['kind']), 'count' => intval($r['cnt']), 'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0];
}
@file_put_contents($kinds_cache, json_encode($kinds));
} catch (PDOException $e) {}
}
// Top pubkeys — cached to file, refreshed every 5 minutes
$top_pubkeys = [];
$pubkeys_cache = $cache_dir . '/stats_pubkeys.json';
if (is_readable($pubkeys_cache) && (time() - filemtime($pubkeys_cache)) < $kinds_ttl) {
$top_pubkeys = json_decode(file_get_contents($pubkeys_cache), true) ?: [];
} else {
try {
$rows = $pdo->query("
SELECT pubkey, COUNT(*) AS cnt
FROM events
GROUP BY pubkey
ORDER BY cnt DESC LIMIT 20
")->fetchAll();
$pubkeys = array_column($rows, 'pubkey');
$pmap = profile_map($pubkeys);
foreach ($rows as $r) {
$pk = $r['pubkey'];
$prof = $pmap[$pk] ?? null;
$top_pubkeys[] = [
'pubkey' => $pk,
'name' => $prof ? $prof['best_name'] : '',
'count' => intval($r['cnt']),
'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0,
];
}
@file_put_contents($pubkeys_cache, json_encode($top_pubkeys));
} catch (PDOException $e) {}
}
// Name-field usage stats (from profiles cache)
$name_field_usage = ['both' => 0, 'name_only' => 0, 'display_only' => 0, 'neither' => 0, 'both_differ' => 0, 'total' => 0];
try {
$row = $pdo->query("
SELECT count(*) FILTER (WHERE name <> '' AND display_name <> '') AS both,
count(*) FILTER (WHERE name <> '' AND display_name = '') AS name_only,
count(*) FILTER (WHERE name = '' AND display_name <> '') AS display_only,
count(*) FILTER (WHERE name = '' AND display_name = '') AS neither,
count(*) FILTER (WHERE name <> '' AND display_name <> '' AND name <> display_name) AS both_differ,
count(*) AS total
FROM profiles
")->fetch();
if ($row) {
$name_field_usage = array_map('intval', $row);
}
} catch (PDOException $e) {}
json_response([
'db_size' => $db_size,
'total_events' => $total_events,
'events_delta' => $events_delta,
'process_id' => $process_id,
'ws_connections' => $ws_connections,
'active_subscriptions' => $active_subscriptions,
'memory_usage' => $memory_usage,
'cpu_usage' => $cpu_usage,
'cpu_core' => $cpu_core,
'oldest_event' => $oldest_event,
'newest_event' => $newest_event,
'events_24h' => $events_24h,
'events_7d' => $events_7d,
'events_30d' => $events_30d,
'kinds' => $kinds,
'top_pubkeys' => $top_pubkeys,
'name_field_usage' => $name_field_usage,
]);
/** Format bytes as human-readable. */
function format_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
+11
View File
@@ -0,0 +1,11 @@
<?php
/** admin2/api/subscriptions.php — Active subscription details. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$subs = [];
try {
$subs = $pdo->query("SELECT id, sub_id, pubkey, filters, created_at, events_sent FROM subscriptions ORDER BY created_at DESC LIMIT 100")->fetchAll();
} catch (PDOException $e) {}
json_response(['subscriptions' => $subs]);
+1328
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
New Events — Last 24 Hours
21 |
19 |
17 |
15 |
13 |
11 | X
9 | X
7 | X
5 | X
3 | X X X
1 | X X X X X XX X X XX X X X X X X X X XX X X X XX X X
+--------------------------------------------------------------------------------
0s 1h 3h 4h 6h 7h 9h 10h 12h 13h 15h 16h 18h 19h 21h 22h
+15
View File
@@ -0,0 +1,15 @@
New Events — Last 30 Days
81 | X
73 | XX X
65 | XX X
57 | XX X X X
49 | X X XX XX X X X
41 | X X X XX X X X XXX XXX XXX X X X X
33 | X X X XXXX XX X X X XX XXX XXXXXXXX XX X X X X X XX
25 |X XX XX XX XXXX XX XXX X XX XXXXXXXXXXXXXXXXXXXX XX XXX X XX XX XXXXX
17 |X XXXXX XX XXXXXXXXXXXXXXX X XX XXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXX XX XXXXXXXX
9 |XXX XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX
1 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+--------------------------------------------------------------------------------
0s 1d 3d 5d 7d 9d 11d 13d 15d 16d 18d 20d 22d 24d 26d 28d
+15
View File
@@ -0,0 +1,15 @@
New Events — Last Year
531 | X XX
478 | X X X XXX X
425 | X X XX X XX XXX X
372 | XXX XX X XX XXX X
319 |XXXXXXXXXXX X X X XXXXXXX X
266 |XXXXXXXXXXXX X XX XXX XXXXXXXXX X
213 |XXXXXXXXXXXXXX XXX XXXXXXXXXXXXX XX X
160 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X XXXX X
107 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX XX XX X X X XX XXX X XXXXXXX X
54 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXX
1 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+--------------------------------------------------------------------------------
0s 22d 45d 68d 91d 114d 136d 159d 182d 205d 228d 250d 273d 296d 319d 342d
+1
View File
@@ -0,0 +1 @@
[]
+1
View File
@@ -0,0 +1 @@
[]
+523
View File
@@ -0,0 +1,523 @@
<?php
/**
* admin2 — Full PHP admin for C-Relay-PG.
* Recreates the original api/ admin interface with a PHP/PDO backend
* instead of Nostr WebSocket admin commands.
*/
require_once __DIR__ . '/lib/helpers.php';
$pdo = db();
// Fetch relay info for the header (from config table)
$relay_name = '';
$relay_desc = '';
$relay_pubkey = '';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_desc = $pdo->query("SELECT value FROM config WHERE key = 'relay_description'")->fetchColumn() ?: '';
$relay_pubkey = $pdo->query("SELECT value FROM config WHERE key = 'relay_pubkey'")->fetchColumn() ?: '';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
// Convert relay pubkey (hex) to npub and format into 3 lines of 3 groups of 7 chars
$relay_npub = $relay_pubkey;
if ($relay_pubkey && preg_match('/^[0-9a-fA-F]{64}$/', $relay_pubkey)) {
$relay_npub = hex_to_npub($relay_pubkey);
}
// Format npub (63 chars) into 3 lines of "xxxxxxx xxxxxxx xxxxxxx"
$formatted_npub = $relay_npub;
if (strlen($relay_npub) === 63) {
$line1 = substr($relay_npub, 0, 7) . ' ' . substr($relay_npub, 7, 7) . ' ' . substr($relay_npub, 14, 7);
$line2 = substr($relay_npub, 21, 7) . ' ' . substr($relay_npub, 28, 7) . ' ' . substr($relay_npub, 35, 7);
$line3 = substr($relay_npub, 42, 7) . ' ' . substr($relay_npub, 49, 7) . ' ' . substr($relay_npub, 56, 7);
$formatted_npub = $line1 . "\n" . $line2 . "\n" . $line3;
}
$display_name = $relay_version ? ($relay_name . ' ' . $relay_version) : $relay_name;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C-Relay-PG Admin</title>
<link rel="stylesheet" href="assets/index.css">
</head>
<body>
<!-- Side Navigation Menu -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<li><button class="nav-item" data-page="statistics">Statistics</button></li>
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="ip-bans">IP BAN</button></li>
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
<li><button class="nav-item" data-page="caching">Caching</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="cleanup">Cleanup</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
<div class="nav-footer">
<button class="nav-footer-btn" id="nav-dark-mode-btn">DARK MODE</button>
</div>
</nav>
<!-- Side Navigation Overlay -->
<div class="side-nav-overlay" id="side-nav-overlay"></div>
<!-- Header with title and profile display -->
<div class="section">
<div class="header-content">
<div class="header-title clickable" id="header-title" onclick="toggleNav()">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name"><?= e($display_name) ?></div>
<div id="relay-description" class="relay-description"><?= e($relay_desc) ?></div>
<div id="relay-pubkey-container" class="relay-pubkey-container" title="Click to copy npub">
<div id="relay-pubkey" class="relay-pubkey"><?= e($formatted_npub) ?></div>
</div>
</div>
<div class="profile-area" id="profile-area" style="display: none;">
<div class="admin-label">admin</div>
<div class="profile-container">
<img id="header-user-image" class="header-user-image" alt="Profile" style="display: none;">
<span id="header-user-name" class="header-user-name">Loading...</span>
</div>
<!-- Logout dropdown (under admin picture) -->
<div class="logout-dropdown" id="logout-dropdown" style="display: none;">
<button type="button" class="logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
</div>
</div>
<!-- Login Modal Overlay (nostr_login_lite) -->
<div id="login-modal" class="login-modal-overlay" style="display: none;">
<div class="login-modal-content">
<div id="login-modal-container"></div>
</div>
</div>
<!-- Access Denied Overlay (shown when non-admin user logs in) -->
<div id="access-denied-overlay" class="access-denied-overlay" style="display: none;">
<div class="access-denied-content">
<div class="access-denied-icon">⛔</div>
<h2>ACCESS DENIED</h2>
<p class="access-denied-message">This interface is restricted to the relay administrator.</p>
<p class="access-denied-submessage">The logged-in account does not have admin privileges.</p>
<button type="button" class="access-denied-logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
<!-- DATABASE STATISTICS Section -->
<div class="section flex-section" id="databaseStatisticsSection">
<div class="section-header">DATABASE STATISTICS</div>
<!-- Event Rate Graph — range selector tabs + ASCII chart (server-rendered) -->
<div class="chart-range-tabs">
<button class="chart-tab active" data-range="hour">1H</button>
<button class="chart-tab" data-range="day">1D</button>
<button class="chart-tab" data-range="month">1M</button>
<button class="chart-tab" data-range="year">1Y</button>
</div>
<div id="event-rate-chart" class="event-rate-chart-container">Loading chart...</div>
<!-- Database Overview Table -->
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="stats-overview-table">
<thead><tr><th>Metric</th><th>Value</th></tr></thead>
<tbody id="stats-overview-table-body">
<tr><td>Database Size</td><td id="db-size">-</td></tr>
<tr><td>Total Events</td><td id="total-events">-</td></tr>
<tr><td>Process ID</td><td id="process-id">-</td></tr>
<tr><td>WebSocket Connections</td><td id="websocket-connections">-</td></tr>
<tr><td>Active Subscriptions</td><td id="active-subscriptions">-</td></tr>
<tr><td>Memory Usage</td><td id="memory-usage">-</td></tr>
<tr><td>CPU Usage</td><td id="cpu-usage">-</td></tr>
<tr><td>CPU Core</td><td id="cpu-core">-</td></tr>
<tr><td>Oldest Event</td><td id="oldest-event">-</td></tr>
<tr><td>Newest Event</td><td id="newest-event">-</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Event Kind Distribution Table -->
<div class="input-group">
<label>Event Kind Distribution:</label>
<div class="config-table-container">
<table class="config-table" id="stats-kinds-table">
<thead><tr><th>Event Kind</th><th>Count</th><th>Percentage</th></tr></thead>
<tbody id="stats-kinds-table-body">
<tr><td colspan="3" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Time-based Statistics Table -->
<div class="input-group">
<label>Time-based Statistics:</label>
<div class="config-table-container">
<table class="config-table" id="stats-time-table">
<thead><tr><th>Period</th><th>Events</th></tr></thead>
<tbody id="stats-time-table-body">
<tr><td>Last 24 Hours</td><td id="events-24h">-</td></tr>
<tr><td>Last 7 Days</td><td id="events-7d">-</td></tr>
<tr><td>Last 30 Days</td><td id="events-30d">-</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Top Pubkeys Table -->
<div class="input-group">
<label>Top Pubkeys by Event Count:</label>
<div class="config-table-container">
<table class="config-table" id="stats-pubkeys-table">
<thead><tr><th>Rank</th><th>Name</th><th>Pubkey</th><th>Event Count</th><th>Percentage</th></tr></thead>
<tbody id="stats-pubkeys-table-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Name-field usage stats (from profiles cache) -->
<div class="input-group" id="name-field-usage" style="display:none;"></div>
</div>
<!-- SUBSCRIPTION DETAILS Section -->
<div class="section flex-section" id="subscriptionDetailsSection" style="display: none;">
<div class="section-header">ACTIVE SUBSCRIPTION DETAILS</div>
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="subscription-details-table">
<thead><tr><th>Sub ID</th><th>Pubkey</th><th>Filters</th><th>Created</th><th>Events Sent</th></tr></thead>
<tbody id="subscription-details-table-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CONFIGURATION Section -->
<div id="div_config" class="section flex-section" style="display: none;">
<div class="section-header">RELAY CONFIGURATION</div>
<div id="config-display">
<div class="config-table-container">
<table class="config-table" id="config-table">
<thead><tr><th>Parameter</th><th>Value</th><th>Actions</th></tr></thead>
<tbody id="config-table-body">
<tr><td colspan="3" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="inline-buttons">
<button type="button" id="fetch-config-btn" onclick="loadConfig()">REFRESH</button>
</div>
</div>
</div>
<!-- AUTH RULES Section -->
<div class="section flex-section" id="authRulesSection" style="display: none;">
<div class="section-header">AUTH RULES MANAGEMENT</div>
<div id="authRulesTableContainer">
<table class="config-table" id="authRulesTable">
<thead><tr><th>Rule Type</th><th>Pattern Type</th><th>Pattern Value</th><th>Status</th><th>Actions</th></tr></thead>
<tbody id="authRulesTableBody">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div id="authRuleInputSections" style="display: block;">
<div class="input-group">
<label for="authRulePubkey">Public Key (npub or hex):</label>
<input type="text" id="authRulePubkey" placeholder="npub1... or 64-character hex pubkey">
</div>
<div class="inline-buttons">
<button type="button" onclick="addAuthRule('whitelist')">ADD TO WHITELIST</button>
<button type="button" onclick="addAuthRule('blacklist')">ADD TO BLACKLIST</button>
<button type="button" onclick="loadAuthRules()">REFRESH</button>
</div>
</div>
</div>
<!-- WEB OF TRUST Section -->
<div class="section flex-section" id="wotSection" style="display: none;">
<div class="section-header">WEB OF TRUST</div>
<div id="wotKind3Status" class="wot-status-row">
<span>Admin Contact List (kind 3):</span>
<span id="wotKind3Indicator" class="wot-indicator wot-unknown">Loading...</span>
</div>
<div class="wot-level-selector">
<label>WoT Level:</label>
<div class="inline-buttons">
<button type="button" class="wot-level-btn" onclick="setWotLevel(0)">OFF</button>
<button type="button" class="wot-level-btn" onclick="setWotLevel(1)">WRITE ONLY</button>
<button type="button" class="wot-level-btn" onclick="setWotLevel(2)">FULL</button>
</div>
<div class="wot-level-description" id="wotLevelDescription">Loading...</div>
</div>
<div class="wot-stats-row">
<span>Whitelisted Pubkeys:</span>
<span id="wotWhitelistCount">—</span>
</div>
<div class="inline-buttons">
<button type="button" onclick="syncWot()">SYNC FROM KIND 3</button>
<button type="button" onclick="loadWotStatus()">REFRESH STATUS</button>
</div>
</div>
<!-- NIP-17 DIRECT MESSAGES Section -->
<div class="section" id="nip17DMSection" style="display: none;">
<div class="section-header"><h2>NIP-17 DIRECT MESSAGES</h2></div>
<div class="input-group">
<label>Received Messages (kind 4/14/15):</label>
<div id="dm-inbox" class="log-panel" style="height: 300px; overflow-y: auto;">
<div class="log-entry">Loading...</div>
</div>
</div>
</div>
<!-- IP BANS Section -->
<div class="section" id="ipBansSection" style="display: none;">
<div class="section-header">IP BAN MANAGEMENT</div>
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="ip-bans-stats-table">
<thead><tr><th>Total IPs Tracked</th><th>Currently Banned</th><th>Total Bans Issued</th></tr></thead>
<tbody>
<tr><td id="ip-bans-total">-</td><td id="ip-bans-active">-</td><td id="ip-bans-issued">-</td></tr>
</tbody>
</table>
</div>
</div>
<div class="input-group">
<h3>Manually Ban IP Address</h3>
<div class="form-group">
<label for="ban-ip-input">IP Address:</label>
<input type="text" id="ban-ip-input" placeholder="192.168.1.100">
</div>
<div class="form-group">
<label for="ban-duration-select">Ban Duration:</label>
<select id="ban-duration-select">
<option value="3600">1 Hour</option>
<option value="86400" selected>24 Hours</option>
<option value="604800">7 Days</option>
<option value="2592000">30 Days</option>
<option value="31536000">1 Year</option>
<option value="999999999">Permanent</option>
</select>
</div>
<div class="inline-buttons">
<button type="button" onclick="addBan()">BAN IP</button>
<button type="button" onclick="loadIpBans()">REFRESH</button>
</div>
<div id="add-ban-status" class="status-message"></div>
</div>
<div class="input-group">
<label>Banned IP Addresses:</label>
<div class="config-table-container">
<table class="config-table" id="ip-bans-table">
<thead><tr><th>IP Address</th><th>Status</th><th>Banned Until</th><th>Failures</th><th>Actions</th></tr></thead>
<tbody id="ip-bans-tbody">
<tr><td colspan="5" style="text-align: center;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RELAY EVENTS Section -->
<div class="section" id="relayEventsSection" style="display: none;">
<div class="section-header">RELAY EVENTS MANAGEMENT</div>
<div class="input-group">
<h3>Recent Events</h3>
<div class="inline-buttons">
<button type="button" onclick="loadEvents()">REFRESH</button>
</div>
<div class="config-table-container">
<table class="config-table" id="live-relay-events-table">
<thead><tr><th>Time</th><th>Kind</th><th>Name</th><th>ID</th><th>Content Preview</th></tr></thead>
<tbody id="live-relay-events-table-body">
<tr><td colspan="5" style="text-align: center;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CACHING Section -->
<div class="section" id="cachingSection" style="display: none;">
<div class="section-header">CACHING</div>
<div class="input-group">
<h3>Caching Controls</h3>
<div id="caching-controls" class="inline-buttons">
<span id="caching-enabled-label">Caching: ?</span>
<button type="button" id="caching-toggle-btn" onclick="toggleCachingEnabled()">Toggle Caching</button>
<span id="caching-inbox-enabled-label">Inbox: ?</span>
<button type="button" id="caching-inbox-toggle-btn" onclick="toggleCachingInboxEnabled()">Toggle Inbox</button>
</div>
</div>
<div class="input-group">
<h3>Caching Service Status</h3>
<div id="caching-service-status" class="status-display">
<p>Loading...</p>
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" id="caching-rerun-all-btn" onclick="rerunAllCaching()">Re-run All Caching</button>
</div>
</div>
<div class="input-group">
<h3>Relay Inbox Status</h3>
<div id="caching-inbox-status" class="status-display">
<p>Loading...</p>
</div>
</div>
<div class="input-group">
<h3>Upstream Relay Status <span style="font-size:11px;font-weight:normal;color:var(--text-muted,#888)">(live connection state, updated every 15s)</span></h3>
<div id="caching-relay-status" class="status-display">
<p>Loading...</p>
</div>
</div>
<div class="input-group">
<h3>Followed Pubkeys <span style="font-size:11px;font-weight:normal;color:var(--text-muted,#888)">(click a row for per-relay details)</span></h3>
<div id="caching-follows-status" class="status-message"></div>
<div id="caching-follows-pagination" class="pagination-bar"></div>
<div class="config-table-container">
<table class="config-table" id="caching-follows-table">
<thead><tr><th>Name</th><th>npub</th><th>Root?</th><th>Events in DB</th><th>Backfill</th><th>Relays</th></tr></thead>
<tbody id="caching-follows-table-body">
<tr><td colspan="6" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="inline-buttons">
<button type="button" onclick="loadCaching()">REFRESH</button>
</div>
</div>
<!-- EVENT CLEANUP Section -->
<div class="section" id="cleanupSection" style="display: none;">
<div class="section-header"><h2>EVENT CLEANUP</h2></div>
<!-- Saved Queries Table -->
<div class="input-group" id="saved-queries-group">
<label>Saved Queries</label>
<div class="config-table-container">
<table class="config-table" id="cleanup-saved-queries-table">
<thead><tr><th>Name</th><th>Filters</th><th>Last Preview</th><th>Last Executed</th><th>Actions</th></tr></thead>
<tbody id="cleanup-saved-queries-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" onclick="newCleanupQuery()">New Query</button>
<button type="button" onclick="loadCleanupQueries()">Refresh</button>
</div>
</div>
<!-- Query Builder -->
<div class="input-group" id="cleanup-builder-group">
<label>Query Builder</label>
<div class="form-group">
<label for="cleanup-name">Query Name:</label>
<input type="text" id="cleanup-name" placeholder="My Cleanup Query" style="width:100%;max-width:400px">
</div>
<div class="form-group">
<label>Follows Filter:</label>
<div class="radio-group">
<label><input type="radio" name="cleanup-follows" value="all" checked> All</label>
<label><input type="radio" name="cleanup-follows" value="follows"> Follows only</label>
<label><input type="radio" name="cleanup-follows" value="non_follows"> Non-follows only</label>
</div>
</div>
<div class="form-group">
<label for="cleanup-kinds">Event Kinds (comma-separated, empty = all):</label>
<input type="text" id="cleanup-kinds" placeholder="e.g. 1, 7, 9734" style="width:100%;max-width:400px">
<div style="font-size:11px;color:var(--text-muted,#888);margin-top:2px">
Common kinds: 0 (profiles), 1 (text), 3 (contacts), 4 (DM), 5 (delete),
6 (repost), 7 (reaction), 9734 (zap request), 9735 (zap receipt), 10002 (relay list)
</div>
</div>
<div class="form-group">
<label for="cleanup-max-age">Max Age (days, 0 = no limit):</label>
<input type="number" id="cleanup-max-age" value="0" min="0" style="width:120px">
</div>
<div class="form-group">
<label for="cleanup-max-events">Max Events (0 = no limit):</label>
<input type="number" id="cleanup-max-events" value="0" min="0" style="width:120px">
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" onclick="previewCleanup()">Preview</button>
<button type="button" onclick="saveCleanupQuery()">Save Query</button>
<button type="button" onclick="executeCleanup()" style="background:#c0392b;color:#fff">Execute Delete</button>
</div>
</div>
<!-- Results Area -->
<div class="input-group" id="cleanup-results-group" style="display:none;">
<label>Results</label>
<div id="cleanup-results-summary" class="info-box"></div>
<div id="cleanup-results-breakdown" class="config-table-container" style="margin-top:8px"></div>
<div id="cleanup-results-sql" style="margin-top:8px">
<details>
<summary style="cursor:pointer;font-size:12px;color:var(--text-muted,#888)">SQL Preview</summary>
<pre id="cleanup-sql-preview" style="background:var(--bg-code,#f5f5f5);padding:8px;border-radius:4px;overflow-x:auto;font-size:11px;margin-top:4px;white-space:pre-wrap"></pre>
</details>
</div>
</div>
<!-- Confirmation Dialog -->
<div id="cleanup-confirm-dialog" class="modal-overlay" style="display:none;">
<div class="modal-content" style="background:var(--bg-card,#fff);border:1px solid var(--border,#ccc);border-radius:8px;padding:20px;max-width:480px;margin:10% auto;box-shadow:0 4px 20px rgba(0,0,0,0.3)">
<h3 style="margin-top:0">Confirm Delete</h3>
<p id="cleanup-confirm-msg">Are you sure you want to delete these events?</p>
<p style="color:#c0392b;font-weight:bold">This action cannot be undone.</p>
<div class="inline-buttons" style="margin-top:12px">
<button type="button" id="cleanup-confirm-btn" onclick="executeCleanupConfirmed()" style="background:#c0392b;color:#fff">Delete</button>
<button type="button" onclick="document.getElementById('cleanup-confirm-dialog').style.display='none'">Cancel</button>
</div>
</div>
</div>
</div>
<!-- SQL QUERY Section -->
<div class="section" id="sqlQuerySection" style="display: none;">
<div class="section-header"><h2>SQL QUERY CONSOLE</h2></div>
<div class="input-group">
<label for="sql-input">SQL Query (SELECT only):</label>
<textarea id="sql-input" rows="5" placeholder="SELECT * FROM events LIMIT 10"></textarea>
</div>
<div class="input-group">
<div class="inline-buttons">
<button type="button" onclick="executeQuery()">EXECUTE QUERY</button>
<button type="button" onclick="document.getElementById('sql-input').value=''">CLEAR</button>
</div>
</div>
<div class="input-group">
<label>Query Results:</label>
<div id="query-info" class="info-box"></div>
<div id="query-table" class="config-table-container"></div>
</div>
</div>
<!-- Nostr libraries for nostr_login_lite auth modal -->
<script src="assets/nostr.bundle.js"></script>
<script src="assets/nostr-lite.js"></script>
<script src="assets/app.js"></script>
</body>
</html>
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* admin2/lib/ascii_chart.php — Server-side ASCII X-bar chart renderer.
*
* Produces a monospaced ASCII bar chart (using 'X' characters) from an
* array of bin counts. Mirrors the layout of the original text_graph.js
* ASCIIBarChart: Y-axis count labels, X-bar columns, X-axis with
* elapsed-time labels.
*
* The output is a plain text string suitable for both browser <div>
* injection (white-space: pre) and terminal display via curl.
*/
/**
* Render an ASCII bar chart from an array of bin counts.
*
* @param array $bins Array of integer counts (one per time bin, left=oldest)
* @param array $options {
* @var string $title Chart title (centered at top)
* @var int $max_height Chart height in rows (default 11)
* @var string $x_axis_label Label below the X-axis (default '')
* @var int $bin_duration Seconds per bin — for X-axis elapsed labels
* @var int $label_interval Label every N bins (default 5)
* }
* @return string The ASCII chart as a multi-line string.
*/
function render_ascii_chart(array $bins, array $options = []): string {
$title = $options['title'] ?? 'New Events';
$max_height = $options['max_height'] ?? 11;
$x_axis_label = $options['x_axis_label'] ?? '';
$bin_duration = $options['bin_duration'] ?? 10;
$label_interval = $options['label_interval'] ?? 5;
$num_bins = count($bins);
if ($num_bins === 0) {
return "No data available.\n";
}
$max_count = max($bins);
if ($max_count < 1) $max_count = 1; // Avoid division by zero for all-empty bins
// Scaling: each X represents scale_factor counts
$scale_factor = max(1, (int)ceil($max_count / $max_height));
$scaled_max = (int)ceil($max_count / $scale_factor) * $scale_factor;
$output = '';
// --- Title (centered) ---
$chart_width = 4 + $num_bins; // 4 = Y-axis number width (3) + separator (1)
if ($title !== '') {
$title_padding = (int)floor(($chart_width - strlen($title)) / 2);
if ($title_padding < 0) $title_padding = 0;
$output .= str_repeat(' ', $title_padding) . $title . "\n\n";
}
// --- Bar rows (top to bottom) ---
for ($row = $max_height; $row > 0; $row--) {
$row_count = ($row - 1) * $scale_factor + 1;
$line = str_pad((string)$row_count, 3, ' ', STR_PAD_LEFT) . ' |';
for ($i = 0; $i < $num_bins; $i++) {
$count = $bins[$i];
$scaled_height = ($count > 0) ? (int)ceil($count / $scale_factor) : 0;
$line .= ($scaled_height >= $row) ? 'X' : ' ';
}
$output .= $line . "\n";
}
// --- X-axis line ---
$output .= ' +' . str_repeat('-', $num_bins) . "\n";
// --- X-axis labels (elapsed time every label_interval bins) ---
$label_line = ' ';
$labels = [];
for ($i = 0; $i < $num_bins; $i++) {
if ($i % $label_interval === 0) {
$elapsed_sec = $i * $bin_duration;
$labels[] = format_elapsed_time($elapsed_sec);
}
}
// Build label line with spacing
for ($i = 0; $i < count($labels); $i++) {
$label_line .= $labels[$i];
if ($i < count($labels) - 1) {
$spacing = $label_interval - strlen($labels[$i]);
if ($spacing < 1) $spacing = 1;
$label_line .= str_repeat(' ', $spacing);
}
}
// Pad to match X-axis dash line length
$min_label_len = 4 + $num_bins;
if (strlen($label_line) < $min_label_len) {
$label_line .= str_repeat(' ', $min_label_len - strlen($label_line));
}
$output .= $label_line . "\n";
// --- X-axis label (if provided) ---
if ($x_axis_label !== '') {
$label_pad = (int)floor(($num_bins - strlen($x_axis_label)) / 2);
if ($label_pad < 0) $label_pad = 0;
$output .= "\n" . ' ' . str_repeat(' ', $label_pad) . $x_axis_label . "\n";
}
return $output;
}
/**
* Format an elapsed time in seconds as a compact label.
* < 60s → "Ns"
* < 1h → "Nm"
* < 1d → "Nh"
* else → "Nd"
*/
function format_elapsed_time(int $seconds): string {
if ($seconds < 60) {
return $seconds . 's';
} elseif ($seconds < 3600) {
return (int)floor($seconds / 60) . 'm';
} elseif ($seconds < 86400) {
return (int)floor($seconds / 3600) . 'h';
} else {
return (int)floor($seconds / 86400) . 'd';
}
}
/**
* Build a fixed-length bin array from SQL query rows.
*
* The query returns only non-empty bins. This function creates a
* zero-filled array of $num_bins length and overlays the counts
* at the correct positions, so empty time slots show as blank
* columns — the chart always advances in time.
*
* @param array $rows Query rows with 'bin' (int) and 'cnt' (int)
* @param int $num_bins Total number of bins (fixed length)
* @return array Zero-filled array of counts
*/
function build_bin_array(array $rows, int $num_bins): array {
$bins = array_fill(0, $num_bins, 0);
foreach ($rows as $r) {
$idx = (int)$r['bin'];
if ($idx >= 0 && $idx < $num_bins) {
$bins[$idx] = (int)$r['cnt'];
}
}
return $bins;
}
+27
View File
@@ -0,0 +1,27 @@
<?php
/**
* C-Relay-PG Admin — Database connection configuration.
*
* This file is NOT web-accessible (denied by nginx location block).
* It contains the PostgreSQL connection string used by all admin pages.
*
* Edit these values to match your relay's PostgreSQL credentials.
*/
// PostgreSQL connection parameters.
// These should match the --db-* flags passed to the c-relay-pg binary
// or the connection string passed to caching_relay -p "...".
return [
'db_host' => 'localhost',
'db_port' => '5432',
'db_name' => 'crelay',
'db_user' => 'crelay',
'db_password' => 'crelay',
// Page sizes for paginated tables.
'follows_per_page' => 50,
'relays_per_page' => 50,
// Auto-refresh interval for dashboard polling (milliseconds).
'refresh_ms' => 10000,
];
+48
View File
@@ -0,0 +1,48 @@
<?php
/**
* C-Relay-PG Admin — PDO database connection helper.
*
* Provides a singleton PDO connection to the relay's PostgreSQL database.
* All admin pages use db() to get the connection.
*/
require_once __DIR__ . '/config.php';
function db(): PDO {
static $pdo = null;
if ($pdo === null) {
$cfg = require __DIR__ . '/config.php';
// Build DSN — supports both TCP (host=localhost) and Unix socket
// (host=/var/run/postgresql). Omit password if empty (peer auth).
$dsn = sprintf(
'pgsql:host=%s;port=%d;dbname=%s;user=%s',
$cfg['db_host'],
$cfg['db_port'],
$cfg['db_name'],
$cfg['db_user']
);
if (!empty($cfg['db_password'])) {
$dsn .= ';password=' . $cfg['db_password'];
}
try {
$pdo = new PDO($dsn, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
http_response_code(500);
die('Database connection failed: ' . htmlspecialchars($e->getMessage()));
}
}
return $pdo;
}
/** Get a config value from the admin config file. */
function cfg(string $key, $default = null) {
static $cfg = null;
if ($cfg === null) {
$cfg = require __DIR__ . '/config.php';
}
return $cfg[$key] ?? $default;
}
+247
View File
@@ -0,0 +1,247 @@
<?php
/**
* C-Relay-PG Admin — Shared helper functions.
*/
require_once __DIR__ . '/db.php';
/** HTML-escape a string. */
function e(?string $s): string {
return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8');
}
/** Format a Unix timestamp as a human-readable relative time. */
function time_ago(int $ts): string {
if ($ts <= 0) return 'never';
$diff = time() - $ts;
if ($diff < 60) return $diff . 's ago';
if ($diff < 3600) return floor($diff / 60) . 'm ago';
if ($diff < 86400) return floor($diff / 3600) . 'h ago';
return floor($diff / 86400) . 'd ago';
}
/** Format a Unix timestamp as a date/time string. */
function fmt_date(int $ts): string {
if ($ts <= 0) return '-';
return date('Y-m-d H:i:s', $ts);
}
/**
* Convert a 64-char hex pubkey to npub (bech32).
* Uses the standard bech32 encoding (NIP-19).
*/
function hex_to_npub(string $hex): string {
if (!preg_match('/^[0-9a-fA-F]{64}$/', $hex)) return $hex;
$hex = strtolower($hex);
// Bech32 constants
$charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
$data = [];
for ($i = 0; $i < strlen($hex); $i += 2) {
$data[] = intval(substr($hex, $i, 2), 16);
}
// Convert 8-bit groups to 5-bit groups
$conv = [];
$buffer = 0;
$bits = 0;
foreach ($data as $byte) {
$buffer = ($buffer << 8) | $byte;
$bits += 8;
while ($bits >= 5) {
$conv[] = ($buffer >> ($bits - 5)) & 31;
$bits -= 5;
}
}
if ($bits > 0) {
$conv[] = ($buffer << (5 - $bits)) & 31;
}
// Compute checksum — HRP values are positions of each char in the charset.
$values = array_merge(
array_map(function($c) {
return strpos('qpzry9x8gf2tvdw0s3jn54khce6mua7l', $c);
}, str_split('npub')),
$conv
);
$gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
$chk = 1;
foreach ($values as $v) {
$top = $chk >> 25;
$chk = (($chk & 0x1ffffff) << 5) ^ $v;
for ($i = 0; $i < 5; $i++) {
if (($top >> $i) & 1) $chk ^= $gen[$i];
}
}
$chk ^= 1;
$checksum = [];
for ($i = 0; $i < 6; $i++) {
$checksum[] = ($chk >> (5 * (5 - $i))) & 31;
}
$result = 'npub1';
foreach (array_merge($conv, $checksum) as $v) {
$result .= $charset[$v];
}
return $result;
}
/** Truncate a string to $len chars with ellipsis. */
function trunc(string $s, int $len = 16): string {
if (strlen($s) <= $len) return $s;
return substr($s, 0, $len) . '…';
}
/** Build pagination links HTML. */
function pagination(int $page, int $per_page, int $total, string $base_url): string {
$total_pages = max(1, ceil($total / $per_page));
if ($total_pages <= 1) return '';
$html = '<div class="pagination">';
if ($page > 1) {
$html .= '<a href="' . e($base_url) . '?page=' . ($page - 1) . '">← Prev</a>';
}
$html .= '<span>Page ' . $page . ' of ' . $total_pages . ' (' . $total . ' total)</span>';
if ($page < $total_pages) {
$html .= '<a href="' . e($base_url) . '?page=' . ($page + 1) . '">Next →</a>';
}
$html .= '</div>';
return $html;
}
/** Get the current page number from the query string. */
function current_page(): int {
$p = intval($_GET['page'] ?? 1);
return max(1, $p);
}
/** JSON response helper for AJAX endpoints. */
function json_response($data): void {
header('Content-Type: application/json');
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
/** Get a query parameter with a default. */
function query_param(string $key, $default = null) {
return $_GET[$key] ?? $default;
}
/**
* Batch-resolve profiles from the profiles cache table.
* Returns [pubkey_hex => ['name'=>..., 'display_name'=>...,
* 'best_name'=>..., 'picture'=>..., 'nip05'=>...]].
* Pubkeys with no cached profile are absent from the result.
*/
function profile_map(array $pubkeys): array {
if (empty($pubkeys)) return [];
$pdo = db();
$map = [];
try {
// Build parameterized IN clause: WHERE pubkey = ANY(?)
// PDO PostgreSQL supports passing an array as a string literal.
$placeholders = implode(',', array_fill(0, count($pubkeys), '?'));
$stmt = $pdo->prepare(
"SELECT pubkey, name, display_name, picture, nip05 FROM profiles "
. "WHERE pubkey IN ($placeholders)"
);
$stmt->execute(array_values($pubkeys));
$rows = $stmt->fetchAll();
foreach ($rows as $r) {
$map[$r['pubkey']] = [
'name' => $r['name'] ?? '',
'display_name' => $r['display_name'] ?? '',
'best_name' => profile_display_name($r),
'picture' => $r['picture'] ?? '',
'nip05' => $r['nip05'] ?? '',
];
}
} catch (PDOException $e) {}
return $map;
}
/**
* Apply the profile_name_preference config key to resolve the display name.
* Never returns null. Returns '' if neither field is set.
*/
function profile_display_name(array $profile): string {
$name = $profile['name'] ?? '';
$display_name = $profile['display_name'] ?? '';
static $pref = null;
if ($pref === null) {
try {
$pdo = db();
$pref = $pdo->query("SELECT value FROM config WHERE key = 'profile_name_preference'")->fetchColumn() ?: 'display_name';
} catch (PDOException $e) {
$pref = 'display_name';
}
}
if ($pref === 'name') {
return $name !== '' ? $name : $display_name;
}
// Default: prefer display_name, fall back to name.
return $display_name !== '' ? $display_name : $name;
}
/**
* Render the shared HTML header with side navigation.
* Pass the active page name to highlight the current nav item.
*/
function admin_header(string $active = '', string $title = 'C-Relay-PG Admin'): void {
$pages = [
'dashboard' => ['index.php', 'Dashboard'],
'follows' => ['follows.php', 'Follows'],
'relays' => ['relays.php', 'Relays'],
'config' => ['config-edit.php', 'Config'],
'inbox' => ['inbox.php', 'Inbox'],
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($title) ?></title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<!-- Side Navigation -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<?php foreach ($pages as $key => [$url, $label]): ?>
<li><a class="nav-item<?= $active === $key ? ' active' : '' ?>" href="<?= e($url) ?>"><?= e($label) ?></a></li>
<?php endforeach; ?>
</ul>
</nav>
<div class="side-nav-overlay" id="side-nav-overlay" onclick="closeNav()"></div>
<!-- Header -->
<div class="main-header">
<div class="header-content">
<button class="menu-btn" onclick="openNav()">☰</button>
<div class="header-title" onclick="location.href='index.php'">
<span class="relay-letter">R</span><span class="relay-letter">E</span><span class="relay-letter">L</span><span class="relay-letter">A</span><span class="relay-letter">Y</span>
</div>
<div style="width:40px"></div>
</div>
</div>
<script>
function openNav() {
document.getElementById('side-nav').classList.add('open');
document.getElementById('side-nav-overlay').classList.add('show');
}
function closeNav() {
document.getElementById('side-nav').classList.remove('open');
document.getElementById('side-nav-overlay').classList.remove('show');
}
</script>
<?php
}
/** Render the shared HTML footer. */
function admin_footer(): void {
?>
</body>
</html>
<?php
}
+3239
View File
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
<?php
/**
* admin/public/index.php — Public relay stats page.
*
* Served at https://<domain>/relay/stats/ (no authentication required).
*
* Shows the relay name/description/pubkey (read-only, from the config table)
* and the server-rendered ASCII event-rate chart. The chart is fetched from
* the public endpoint at /relay/api/chart.php?range=...
*
* This page exposes ONLY read-only, non-sensitive information:
* - relay_name, relay_description, relay_pubkey, relay_version (from config)
* - the event-rate chart (aggregate COUNT query, no user data)
*
* All sensitive admin endpoints (config edits, auth rules, IP bans, DMs,
* raw SQL queries) remain behind Basic Auth at /relay/admin/.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// Fetch relay info for the header (from config table).
$relay_name = '';
$relay_desc = '';
$relay_pubkey = '';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_desc = $pdo->query("SELECT value FROM config WHERE key = 'relay_description'")->fetchColumn() ?: '';
$relay_pubkey = $pdo->query("SELECT value FROM config WHERE key = 'relay_pubkey'")->fetchColumn() ?: '';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
// Convert relay pubkey (hex) to npub and format into 3 lines of 3 groups of 7 chars.
$relay_npub = $relay_pubkey;
if ($relay_pubkey && preg_match('/^[0-9a-fA-F]{64}$/', $relay_pubkey)) {
$relay_npub = hex_to_npub($relay_pubkey);
}
$formatted_npub = $relay_npub;
if (strlen($relay_npub) === 63) {
$line1 = substr($relay_npub, 0, 7) . ' ' . substr($relay_npub, 7, 7) . ' ' . substr($relay_npub, 14, 7);
$line2 = substr($relay_npub, 21, 7) . ' ' . substr($relay_npub, 28, 7) . ' ' . substr($relay_npub, 35, 7);
$line3 = substr($relay_npub, 42, 7) . ' ' . substr($relay_npub, 49, 7) . ' ' . substr($relay_npub, 56, 7);
$formatted_npub = $line1 . "\n" . $line2 . "\n" . $line3;
}
$display_name = $relay_version ? ($relay_name . ' ' . $relay_version) : $relay_name;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($display_name) ?></title>
<link rel="stylesheet" href="/relay/assets/index.css">
<style>
/* Public page uses the admin theme but hides the nav and centers content. */
.side-nav, .side-nav-overlay { display: none !important; }
.public-wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }
.public-chart-box {
background: var(--secondary-color);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
padding: 12px;
margin-top: 16px;
}
.public-footer {
margin-top: 24px;
padding-top: 12px;
border-top: var(--border-width) solid var(--border-color);
font-size: 12px;
color: var(--muted-color);
text-align: center;
}
.public-footer a { color: var(--accent-color); text-decoration: none; }
.event-rate-chart-container {
white-space: pre;
font-family: var(--font-family);
font-size: 12px;
line-height: 1.2;
overflow-x: auto;
}
</style>
</head>
<body>
<!-- Header with title and relay info (read-only) -->
<div class="section">
<div class="header-content">
<div class="header-title">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name"><?= e($display_name) ?></div>
<div id="relay-description" class="relay-description"><?= e($relay_desc) ?></div>
<div id="relay-pubkey-container" class="relay-pubkey-container" title="Click to copy npub">
<div id="relay-pubkey" class="relay-pubkey"><?= e($formatted_npub) ?></div>
</div>
</div>
</div>
</div>
<div class="public-wrap">
<div class="section-header">EVENT RATE</div>
<!-- Range selector tabs -->
<div class="chart-range-tabs">
<button class="chart-tab active" data-range="hour">1H</button>
<button class="chart-tab" data-range="day">1D</button>
<button class="chart-tab" data-range="month">1M</button>
<button class="chart-tab" data-range="year">1Y</button>
</div>
<!-- Server-rendered ASCII chart (fetched from the public chart endpoint) -->
<div class="public-chart-box">
<div id="event-rate-chart" class="event-rate-chart-container">Loading chart...</div>
</div>
<div class="public-footer">
Public relay statistics &middot;
<a href="wss://<?= e($_SERVER['HTTP_HOST'] ?? '') ?>">wss://<?= e($_SERVER['HTTP_HOST'] ?? '') ?></a>
&middot; Admin? visit <a href="/relay/admin/">/relay/admin/</a>
</div>
</div>
<script>
// Minimal chart loader — fetches the public ASCII chart endpoint and
// injects the plain-text response into the chart container. No auth.
const chartEl = document.getElementById('event-rate-chart');
let currentRange = 'hour';
let chartTimer = null;
async function loadChart(range) {
currentRange = range;
if (!chartEl) return;
chartEl.textContent = 'Loading chart...';
try {
const res = await fetch('/relay/api/chart.php?range=' + encodeURIComponent(range));
if (!res.ok) { chartEl.textContent = 'Chart load failed (HTTP ' + res.status + ')'; return; }
const text = await res.text();
chartEl.textContent = text || '(no data)';
} catch (e) {
chartEl.textContent = 'Chart load failed: ' + e.message;
}
}
document.querySelectorAll('.chart-tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.chart-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const range = btn.getAttribute('data-range');
loadChart(range);
// Hour chart is live — auto-refresh every 30s. Others are cached server-side.
if (chartTimer) { clearInterval(chartTimer); chartTimer = null; }
if (range === 'hour') {
chartTimer = setInterval(() => loadChart(currentRange), 30000);
}
});
});
// Initial load + live refresh for the default (hour) range.
loadChart('hour');
chartTimer = setInterval(() => loadChart(currentRange), 30000);
// Click-to-copy the relay npub.
const pkContainer = document.getElementById('relay-pubkey-container');
if (pkContainer) {
pkContainer.style.cursor = 'pointer';
pkContainer.addEventListener('click', () => {
const txt = (document.getElementById('relay-pubkey').textContent || '').replace(/\s+/g, '');
if (txt && navigator.clipboard) navigator.clipboard.writeText(txt);
});
}
</script>
</body>
</html>
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# Launch the admin2 PHP built-in server, fully detached from the calling shell.
# Usage: ./admin2/serve.sh [port] (default port 8088)
PORT="${1:-8088}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Kill any existing instance on this port.
pkill -f "php -S 127.0.0.1:$PORT" 2>/dev/null
sleep 1
# Start fully detached in its own session so SIGTERM to the launcher
# does not propagate to the PHP server.
setsid php -S 127.0.0.1:$PORT -t "$SCRIPT_DIR" > "$SCRIPT_DIR/php_server.log" 2>&1 < /dev/null &
PHP_PID=$!
disown $PHP_PID 2>/dev/null
# Give it a moment to bind, then report.
sleep 1
if kill -0 "$PHP_PID" 2>/dev/null; then
echo "admin PHP server started on http://127.0.0.1:$PORT (PID $PHP_PID)"
echo "Serving from: $SCRIPT_DIR"
echo "Log: $SCRIPT_DIR/php_server.log"
else
echo "ERROR: PHP server failed to start. Log:"
cat "$SCRIPT_DIR/php_server.log" 2>/dev/null
exit 1
fi
+191
View File
@@ -1291,6 +1291,184 @@ body.dark-mode .sql-results-table tbody tr:nth-child(even) {
/* background-color: var(--secondary-color); */
}
/* ================================
WEB OF TRUST (WoT) STYLES
================================ */
.wot-status-row, .wot-stats-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
font-size: 14px;
}
.wot-indicator {
padding: 2px 10px;
border-radius: 4px;
font-weight: bold;
font-size: 12px;
}
.wot-indicator.wot-found { background: #28a745; color: white; }
.wot-indicator.wot-missing { background: #dc3545; color: white; }
.wot-indicator.wot-unknown { background: #6c757d; color: white; }
.wot-level-selector { padding: 10px 0; }
.wot-level-selector label { display: block; margin-bottom: 5px; font-weight: bold; }
.wot-level-btn { min-width: 100px; }
.wot-level-btn.active {
background: var(--accent-color, #ff0000);
color: white;
border-color: var(--accent-color, #ff0000);
}
.wot-level-description {
font-size: 12px;
color: var(--primary-color);
margin-top: 5px;
font-style: italic;
opacity: 0.8;
}
/* Dark mode adjustments for WoT */
body.dark-mode .wot-indicator.wot-found { background: #28a745; }
body.dark-mode .wot-indicator.wot-missing { background: #dc3545; }
body.dark-mode .wot-indicator.wot-unknown { background: #6c757d; }
/* ================================
ADMIN ACCESS GATE STYLES
================================ */
/* Access Denied Overlay */
.access-denied-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 9999;
display: none;
justify-content: center;
align-items: center;
}
.access-denied-content {
background: var(--card-bg);
border: 2px solid #dc3545;
border-radius: 12px;
padding: 40px 60px;
text-align: center;
max-width: 500px;
box-shadow: 0 10px 40px rgba(220, 53, 69, 0.3);
}
.access-denied-icon {
font-size: 64px;
margin-bottom: 20px;
}
.access-denied-content h2 {
color: #dc3545;
font-size: 32px;
margin-bottom: 20px;
letter-spacing: 2px;
}
.access-denied-message {
font-size: 16px;
color: var(--primary-color);
margin-bottom: 10px;
line-height: 1.5;
}
.access-denied-submessage {
font-size: 14px;
color: var(--muted-color);
margin-bottom: 30px;
font-style: italic;
}
.access-denied-logout-btn {
background: #dc3545;
color: white;
border: none;
padding: 12px 40px;
font-size: 16px;
font-weight: bold;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.access-denied-logout-btn:hover {
background: #c82333;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
}
/* Admin Verification Loading Overlay */
.admin-verification-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 9998;
display: none;
justify-content: center;
align-items: center;
}
.admin-verification-content {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 40px 60px;
text-align: center;
max-width: 450px;
}
.admin-verification-content h3 {
color: var(--accent-color);
font-size: 20px;
margin-bottom: 15px;
}
.admin-verification-content p {
color: var(--muted-color);
font-size: 14px;
margin-top: 15px;
}
/* Spinner Animation */
.spinner {
width: 50px;
height: 50px;
border: 4px solid var(--border-color);
border-top: 4px solid var(--accent-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Dark mode adjustments */
body.dark-mode .access-denied-content {
background: var(--card-bg);
}
body.dark-mode .admin-verification-content {
background: var(--card-bg);
}
.subscription-detail-row:hover {
background-color: var(--muted-color);
}
@@ -1308,3 +1486,16 @@ body.dark-mode .sql-results-table tbody tr:nth-child(even) {
font-size: 12px;
}
/* ================================
IP BANS TABLE - compact rows
================================ */
#ip-bans-table td, #ip-bans-table th {
padding: 4px 8px;
line-height: 1.3;
font-size: 13px;
}
#ip-bans-table button {
padding: 2px 8px;
font-size: 12px;
}
+359 -16
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C-Relay Admin</title>
<title>C-Relay-PG Admin</title>
<link rel="stylesheet" href="/api/index.css">
</head>
@@ -16,7 +16,9 @@
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="ip-bans">IP BANS</button></li>
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
<li><button class="nav-item" data-page="caching">Caching</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
@@ -41,7 +43,7 @@
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name">C-Relay</div>
<div id="relay-name" class="relay-name">C-Relay-PG</div>
<div id="relay-description" class="relay-description">Loading...</div>
<div id="relay-pubkey-container" class="relay-pubkey-container">
<div id="relay-pubkey" class="relay-pubkey">Loading...</div>
@@ -67,6 +69,17 @@
</div>
</div>
<!-- Access Denied Overlay (shown when non-admin user logs in) -->
<div id="access-denied-overlay" class="access-denied-overlay" style="display: none;">
<div class="access-denied-content">
<div class="access-denied-icon"></div>
<h2>ACCESS DENIED</h2>
<p class="access-denied-message">This interface is restricted to the relay administrator.</p>
<p class="access-denied-submessage">The logged-in account does not have admin privileges.</p>
<button type="button" class="access-denied-logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
<!-- DATABASE STATISTICS Section -->
<!-- Subscribe to kind 24567 events to receive real-time monitoring data -->
<div class="section flex-section" id="databaseStatisticsSection" style="display: none;">
@@ -100,6 +113,10 @@
<td>Process ID</td>
<td id="process-id">-</td>
</tr>
<tr>
<td>WebSocket Connections</td>
<td id="websocket-connections">-</td>
</tr>
<tr>
<td>Active Subscriptions</td>
<td id="active-subscriptions">-</td>
@@ -108,14 +125,14 @@
<td>Memory Usage</td>
<td id="memory-usage">-</td>
</tr>
<tr>
<td>CPU Core</td>
<td id="cpu-core">-</td>
</tr>
<tr>
<td>CPU Usage</td>
<td id="cpu-usage">-</td>
</tr>
<tr>
<td>CPU Core</td>
<td id="cpu-core">-</td>
</tr>
<tr>
<td>Oldest Event</td>
<td id="oldest-event">-</td>
@@ -187,6 +204,7 @@
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th>Pubkey</th>
<th>Event Count</th>
<th>Percentage</th>
@@ -194,7 +212,7 @@
</thead>
<tbody id="stats-pubkeys-table-body">
<tr>
<td colspan="4" style="text-align: center; font-style: italic;">No data loaded</td>
<td colspan="5" style="text-align: center; font-style: italic;">No data loaded</td>
</tr>
</tbody>
</table>
@@ -251,6 +269,7 @@
</div>
<!-- Auth Rules Management - Moved after configuration -->
<!-- AUTH RULES MANAGEMENT SECTION -->
<div class="section flex-section" id="authRulesSection" style="display: none;">
<div class="section-header">
AUTH RULES MANAGEMENT
@@ -273,16 +292,11 @@ AUTH RULES MANAGEMENT
</table>
</div>
<!-- Simplified Auth Rule Input Section -->
<!-- Auth Rule Input Section -->
<div id="authRuleInputSections" style="display: block;">
<!-- Combined Pubkey Auth Rule Section -->
<div class="input-group">
<label for="authRulePubkey">Pubkey (nsec or hex):</label>
<input type="text" id="authRulePubkey" placeholder="nsec1... or 64-character hex pubkey">
<label for="authRulePubkey">Public Key (npub or hex):</label>
<input type="text" id="authRulePubkey" placeholder="npub1... or 64-character hex pubkey">
</div>
<div id="whitelistWarning" class="warning-box" style="display: none;">
<strong>⚠️ WARNING:</strong> Adding whitelist rules changes relay behavior to whitelist-only
@@ -296,10 +310,51 @@ AUTH RULES MANAGEMENT
BLACKLIST</button>
<button type="button" id="refreshAuthRulesBtn">REFRESH</button>
</div>
</div>
</div>
<!-- WEB OF TRUST SECTION -->
<div class="section flex-section" id="wotSection" style="display: none;">
<div class="section-header">
WEB OF TRUST
</div>
<!-- Kind 3 Status Indicator -->
<div id="wotKind3Status" class="wot-status-row">
<span>Admin Contact List (kind 3):</span>
<span id="wotKind3Indicator" class="wot-indicator wot-unknown">Checking...</span>
</div>
<!-- WoT Level Selector -->
<div class="wot-level-selector">
<label>WoT Level:</label>
<div class="inline-buttons">
<button type="button" id="wotLevel0Btn" class="wot-level-btn" onclick="setWotLevel(0)">
OFF
</button>
<button type="button" id="wotLevel1Btn" class="wot-level-btn" onclick="setWotLevel(1)">
WRITE ONLY
</button>
<button type="button" id="wotLevel2Btn" class="wot-level-btn" onclick="setWotLevel(2)">
FULL
</button>
</div>
<div class="wot-level-description" id="wotLevelDescription">
Level 0: Open relay — anyone can read and write
</div>
</div>
<!-- WoT Stats -->
<div class="wot-stats-row">
<span>Whitelisted Pubkeys:</span>
<span id="wotWhitelistCount"></span>
</div>
<!-- Sync Button -->
<div class="inline-buttons">
<button type="button" id="wotSyncBtn" onclick="syncWot()">SYNC FROM KIND 3</button>
<button type="button" id="wotRefreshBtn" onclick="loadWotStatus()">REFRESH STATUS</button>
</div>
</div>
@@ -334,12 +389,138 @@ AUTH RULES MANAGEMENT
</div>
</div>
<!-- IP BANS Section -->
<div class="section" id="ipBansSection" style="display: none;">
<div class="section-header">
IP BAN MANAGEMENT
</div>
<!-- Statistics Cards -->
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="ip-bans-stats-table">
<thead>
<tr>
<th>Total IPs Tracked</th>
<th>Currently Banned</th>
<th>Total Bans Issued</th>
</tr>
</thead>
<tbody>
<tr>
<td id="ip-bans-total">-</td>
<td id="ip-bans-active">-</td>
<td id="ip-bans-issued">-</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Add Ban Form -->
<div class="input-group">
<h3>Manually Ban IP Address</h3>
<div class="form-group">
<label for="ban-ip-input">IP Address:</label>
<input type="text" id="ban-ip-input" placeholder="192.168.1.100">
</div>
<div class="form-group">
<label for="ban-duration-select">Ban Duration:</label>
<select id="ban-duration-select">
<option value="3600">1 Hour</option>
<option value="86400" selected>24 Hours</option>
<option value="604800">7 Days</option>
<option value="2592000">30 Days</option>
<option value="31536000">1 Year</option>
<option value="999999999">Permanent</option>
</select>
</div>
<div class="inline-buttons">
<button type="button" id="add-ban-btn">BAN IP</button>
</div>
<div id="add-ban-status" class="status-message"></div>
</div>
<!-- Whitelist Management -->
<div class="input-group">
<h3>IP Whitelist (Never Banned)</h3>
<p style="font-size:13px;opacity:0.8;">IPs in this list are never idle-banned. Comma-separated.</p>
<div class="form-group">
<label for="whitelist-ip-input">Add IP to Whitelist:</label>
<input type="text" id="whitelist-ip-input" placeholder="103.81.231.220">
</div>
<div class="inline-buttons">
<button type="button" id="add-whitelist-btn">ADD TO WHITELIST</button>
</div>
<div id="whitelist-status" class="status-message"></div>
<div id="whitelist-current" style="margin-top:8px;font-size:13px;"></div>
</div>
<!-- Filter Controls -->
<div class="input-group">
<div class="inline-buttons">
<button type="button" id="ip-ban-filter-all" class="active">All IPs</button>
<button type="button" id="ip-ban-filter-banned">Currently Banned</button>
<button type="button" id="ip-ban-filter-expired">Expired</button>
<button type="button" id="refresh-ip-bans-btn">REFRESH</button>
</div>
</div>
<!-- IP Bans List -->
<div class="input-group">
<label>Banned IP Addresses:</label>
<div class="config-table-container">
<table class="config-table" id="ip-bans-table">
<thead>
<tr>
<th>IP Address</th>
<th>Status</th>
<th>Banned Until</th>
<th>Failures</th>
<th>Authed Successfully</th>
<th>Connection Attempts</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ip-bans-tbody">
<tr>
<td colspan="7" style="text-align: center;">Click REFRESH to load IP bans</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RELAY EVENTS Section -->
<div class="section" id="relayEventsSection" style="display: none;">
<div class="section-header">
RELAY EVENTS MANAGEMENT
</div>
<!-- Live Relay Event Feed -->
<div class="input-group">
<h3>Live Relay Event Feed (Normal Nostr Subscription)</h3>
<div class="config-table-container">
<table class="config-table" id="live-relay-events-table">
<thead>
<tr>
<th>Time</th>
<th>Kind</th>
<th>Pubkey</th>
<th>ID</th>
<th>Content Preview</th>
</tr>
</thead>
<tbody id="live-relay-events-table-body">
<tr>
<td colspan="5" style="text-align: center;">Waiting for live events...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Kind 0: User Metadata -->
<div class="input-group">
<h3>Kind 0: User Metadata</h3>
@@ -400,6 +581,164 @@ AUTH RULES MANAGEMENT
</div>
</div>
<!-- CACHING Section -->
<div class="section" id="cachingSection" style="display: none;">
<div class="section-header">CACHING</div>
<!-- Caching Service Status (read-only) -->
<div class="input-group">
<h3>Caching Service Status</h3>
<div id="caching-service-status" class="status-display">
<p>Service status will appear here after connecting.</p>
</div>
</div>
<!-- Relay Inbox Status (read-only) -->
<div class="input-group">
<h3>Relay Inbox Status</h3>
<div id="caching-inbox-status" class="status-display">
<p>Inbox status will appear here after connecting.</p>
</div>
</div>
<!-- Caching Configuration (editable) -->
<div class="input-group">
<h3>Caching Configuration</h3>
<div class="form-group">
<label for="caching-enabled">Enable Caching:</label>
<select id="caching-enabled">
<option value="false">Disabled</option>
<option value="true">Enabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-inbox-enabled">Enable Inbox Consumer:</label>
<select id="caching-inbox-enabled">
<option value="false">Disabled</option>
<option value="true">Enabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-root-npubs">Root Npubs (one per line):</label>
<textarea id="caching-root-npubs" rows="4" placeholder="npub1...">npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks</textarea>
</div>
<div class="form-group">
<label for="caching-bootstrap-relays">Bootstrap Relays (one per line):</label>
<textarea id="caching-bootstrap-relays" rows="4" placeholder="wss://relay.damus.io">wss://relay.damus.io
wss://nos.lol
wss://relay.primal.net
wss://laantungir.net/relay</textarea>
</div>
<div class="form-group">
<label for="caching-kinds">Kinds (comma-separated):</label>
<input type="text" id="caching-kinds" placeholder="1,3,6,10000,30023" value="0,1,3,6,10000,10002,30023">
</div>
<div class="form-group">
<label for="caching-admin-kinds">Admin Kinds (comma-separated, * for all):</label>
<input type="text" id="caching-admin-kinds" placeholder="*">
</div>
<div class="form-group">
<label for="caching-live-enabled">Live Subscriptions:</label>
<select id="caching-live-enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-backfill-enabled">Backfill:</label>
<select id="caching-backfill-enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-backfill-windows">Backfill Windows (seconds, comma-separated):</label>
<input type="text" id="caching-backfill-windows" placeholder="86400,604800,2592000,7776000,31536000">
</div>
<div class="form-group">
<label for="caching-backfill-page-size">Backfill Page Size:</label>
<input type="number" id="caching-backfill-page-size" placeholder="50">
</div>
<div class="form-group">
<label for="caching-backfill-tick-interval">Backfill Tick Interval (ms):</label>
<input type="number" id="caching-backfill-tick-interval" placeholder="5000">
</div>
<div class="form-group">
<label for="caching-inbox-batch-size">Inbox Batch Size:</label>
<input type="number" id="caching-inbox-batch-size" placeholder="150">
</div>
<div class="form-group">
<label for="caching-inbox-active-poll">Inbox Active Poll (ms):</label>
<input type="number" id="caching-inbox-active-poll" placeholder="200">
</div>
<div class="form-group">
<label for="caching-inbox-idle-poll">Inbox Idle Poll (ms):</label>
<input type="number" id="caching-inbox-idle-poll" placeholder="5000">
</div>
<div class="form-group">
<label for="caching-service-binary-path">Caching Service Binary Path:</label>
<input type="text" id="caching-service-binary-path" placeholder="./caching_relay" value="./caching_relay">
</div>
<div class="form-group">
<label for="caching-service-pg-conn">Caching Service PG Connection:</label>
<input type="text" id="caching-service-pg-conn" placeholder="host=localhost port=5432 dbname=crelay user=crelay password=crelay" value="host=localhost port=5432 dbname=crelay user=crelay password=crelay">
</div>
<div class="inline-buttons">
<button type="button" id="caching-apply-btn">APPLY CONFIGURATION</button>
<button type="button" id="caching-reset-progress-btn">RESET BACKFILL PROGRESS</button>
</div>
<div id="caching-config-status" class="status-message"></div>
</div>
<div class="input-group">
<h3>Caching Service Control</h3>
<p>Start or stop the external caching service process. Set <code>caching_service_binary_path</code> and <code>caching_service_pg_conn</code> above and click APPLY CONFIGURATION before starting.</p>
<div class="inline-buttons">
<button type="button" id="caching-start-service-btn">START CACHING SERVICE</button>
<button type="button" id="caching-stop-service-btn">STOP CACHING SERVICE</button>
</div>
<div id="caching-service-control-status" class="status-message"></div>
</div>
<div class="input-group">
<h3>Followed Pubkeys</h3>
<p>Pubkeys being cached with their profile names, event counts, outbox relays, and backfill status.</p>
<div id="caching-follows-status" class="status-message"></div>
<table id="caching-follows-table" style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="border-bottom: 2px solid var(--border-color, #444); text-align: left;">
<th style="padding: 6px 8px;">Name</th>
<th style="padding: 6px 8px;">npub</th>
<th style="padding: 6px 8px;">Root?</th>
<th style="padding: 6px 8px;">Events in DB</th>
<th style="padding: 6px 8px;">Backfill</th>
<th style="padding: 6px 8px; width: 40%;">Relays (status, events fetched)</th>
</tr>
</thead>
<tbody id="caching-follows-table-body">
<tr><td colspan="6" style="text-align: center; font-style: italic; padding: 12px;">Loading follows status...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- SQL QUERY Section -->
<div class="section" id="sqlQuerySection" style="display: none;">
<div class="section-header">
@@ -419,6 +758,10 @@ AUTH RULES MANAGEMENT
<option value="event_kinds">Event Kinds Distribution</option>
<option value="time_stats">Time-based Statistics</option>
</optgroup>
<optgroup label="IP Ban Queries">
<option value="banned_ips_summary">Banned IPs Summary</option>
<option value="banned_ips_list">All Banned IP Addresses</option>
</optgroup>
<optgroup label="Query History" id="history-group">
<!-- Dynamically populated from localStorage -->
</optgroup>
+1791 -103
View File
File diff suppressed because it is too large Load Diff
+95 -1
View File
@@ -3297,8 +3297,18 @@ var NostrTools = (() => {
this.enablePing = opts.enablePing;
}
async ensureRelay(url, params) {
const rawUrl = url;
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
url = normalizeURL(url);
let relay = this.relays.get(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay START", {
rawUrl,
normalizedUrl: url,
hadRelay: !!relay,
relayMapKeysBefore: Array.from(this.relays.keys())
});
}
if (!relay) {
relay = new AbstractRelay(url, {
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
@@ -3306,13 +3316,38 @@ var NostrTools = (() => {
enablePing: this.enablePing
});
relay.onclose = () => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay onclose", {
normalizedUrl: url,
relayMapKeysBeforeDelete: Array.from(this.relays.keys())
});
}
this.relays.delete(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay onclose complete", {
normalizedUrl: url,
relayMapKeysAfterDelete: Array.from(this.relays.keys())
});
}
};
if (params?.connectionTimeout)
relay.connectionTimeout = params.connectionTimeout;
this.relays.set(url, relay);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay created relay", {
normalizedUrl: url,
relayMapKeysAfterCreate: Array.from(this.relays.keys())
});
}
}
await relay.connect();
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay connected", {
normalizedUrl: url,
relayConnected: relay.connected,
relayMapKeysAfterConnect: Array.from(this.relays.keys())
});
}
return relay;
}
close(relays) {
@@ -3334,16 +3369,26 @@ var NostrTools = (() => {
}
subscribeMany(relays, filters, params) {
params.onauth = params.onauth || params.doauth;
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const request = [];
const uniqUrls = [];
for (let i2 = 0; i2 < relays.length; i2++) {
const url = normalizeURL(relays[i2]);
if (uniqUrls.indexOf(url) === -1) {
uniqUrls.push(url);
for (let f2 = 0; f2 < filters.length; f2++) {
request.push({ url, filter: filters[f2] });
}
}
}
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMany", {
relaysInput: relays,
uniqUrls,
filtersCount: filters.length,
requestsCount: request.length
});
}
return this.subscribeMap(request, params);
}
subscribeMap(requests, params) {
@@ -3391,14 +3436,31 @@ var NostrTools = (() => {
_knownIds.add(id);
return have;
};
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const allOpened = Promise.all(
requests.map(async ({ url, filter }, i2) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMap request", {
index: i2,
url,
filterKinds: filter?.kinds,
hasAuthorFilter: !!filter?.authors,
hasPTagFilter: !!filter?.["#p"]
});
}
let relay;
try {
relay = await this.ensureRelay(url, {
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
});
} catch (err) {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMap ensureRelay FAILED", {
index: i2,
url,
error: err?.message || String(err)
});
}
handleClose(i2, err?.message || String(err));
return;
}
@@ -3480,18 +3542,50 @@ var NostrTools = (() => {
return events[0] || null;
}
publish(relays, event, options) {
return relays.map(normalizeURL).map(async (url, i2, arr) => {
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const normalizedRelays = relays.map(normalizeURL);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish START", {
relaysInput: relays,
normalizedRelays,
eventKind: event?.kind,
eventId: event?.id
});
}
return normalizedRelays.map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish duplicate URL", { url, index: i2 });
}
return Promise.reject("duplicate url");
}
let r = await this.ensureRelay(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish ensured relay", {
url,
relayConnected: r?.connected,
relayMapKeys: Array.from(this.relays.keys())
});
}
return r.publish(event).catch(async (err) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish ERROR", {
url,
error: err?.message || String(err)
});
}
if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
await r.auth(options.onauth);
return r.publish(event);
}
throw err;
}).then((reason) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish SUCCESS", {
url,
reason
});
}
if (this.trackRelays) {
let set = this.seenOn.get(event.id);
if (!set) {
+813
View File
@@ -0,0 +1,813 @@
<!DOCTYPE html>
<?xml version="1.0" encoding="UTF-8"?>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>TEMPLATE</title>
<link rel="stylesheet" href="./css/client.css" />
<!-- Initialize theme BEFORE any components load -->
<script>
(function () {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark-mode');
if (document.body) {
document.body.classList.add('dark-mode');
}
}
})();
</script>
<link rel="shortcut icon" type="image/x-icon" href="./favicon/favicon-dots2.ico" />
<!-- SVG.js library (required by HamburgerMorphing) -->
<script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.0/dist/svg.min.js"></script>
</head>
<body>
<!-- ================================================================
HAMBURGER BUTTON (Fixed, separate from header)
================================================================
The hamburger button is a fixed element outside the header
to ensure it stays visible above the sidenav (z-index: 10 > 3).
================================================================ -->
<div id="divSvgHam" class="divHeaderButtons">
<!-- HamburgerMorphing will be injected here -->
</div>
<!-- ================================================================
HEADER
================================================================
Standard header with title (center).
================================================================ -->
<div id="divHeader">
<div id="divHeaderFlexLeft">
<!-- Hamburger is now separate fixed element -->
</div>
<div id="divHeaderFlexCenter">
<div class="divHeaderText"></div>
</div>
<div id="divHeaderFlexRight">
<!-- No button in header right - logout is in sidenav footer -->
</div>
</div>
<!-- ================================================================
BODY
================================================================
Main content area. Add your page-specific content here.
================================================================ -->
<div id="divBody">
</div>
<!-- ================================================================
FOOTER
================================================================
Three-section footer layout:
- Left: Relay status animations (HamburgerMorphing instances)
- Center: General status information
- Right: Additional information
================================================================ -->
<div id="divFooter">
<div id="divFooterLeft" class="divFooterBox"></div>
<div id="divFooterCenter" class="divFooterBox"></div>
<div id="divFooterRight" class="divFooterBox"></div>
<div id="divFooterBalance" class="divFooterBox">0 sats</div>
</div>
<!-- ================================================================
SIDENAV
================================================================
Slide-out navigation panel. Opens from left when hamburger clicked.
Uses flexbox layout to pin version bar to bottom.
Includes a version bar footer with theme toggle and logout buttons.
================================================================ -->
<div id="divSideNav">
<div id="divSideNavHeader">
<!-- No close button - use main hamburger to close -->
</div>
<div id="divSideNavBody">
<div id="divFiles"></div>
</div>
<div id="divAiSection" class="sidenavSection">
<div id="divAiSectionTitle" class="sidenavSectionTitle">AI</div>
<div id="divAiList" class="sidenavSectionList">
<div id="divAiProvidersList">No saved providers yet.</div>
</div>
</div>
<div id="divRelaySection">
<div id="divRelaySectionTitle">
リレー
</div>
<div id="divRelayList">
Loading relays...
</div>
</div>
<div id="divBlossomSection">
<div id="divBlossomSectionTitle">ブロッサム</div>
<div id="divBlossomList">Loading blossom servers...</div>
</div>
<div id="divVersionBar">
<span id="versionDisplay">v0.0.1</span>
<div id="divVersionBarButtons">
<button id="themeToggleButton" title="Toggle Dark/Light Mode">
<div id="themeToggleHamburgerContainer"></div>
</button>
<button id="logoutButton" title="Logout">
<div id="logoutHamburgerContainer"></div>
</button>
</div>
</div>
</div>
<!-- ================================================================
REQUIRED SCRIPTS
================================================================
These scripts must be loaded in this order:
1. nostr.bundle.js - Nostr tools library
2. nostr-lite.js - Authentication modal (nostr-login-lite)
================================================================ -->
<script src="./nostr.bundle.js"></script>
<script src="./nostr-lite.js"></script>
<script type="module">
/* ================================================================
IMPORTS
================================================================
Import shared NDK functionality from init-ndk.mjs:
- initNDKPage() - Initialize authentication and worker
- getPubkey() - Get current user's pubkey
- subscribe() - Create NDK subscriptions
- publishEvent() - Publish events via NDK
- disconnect() - Disconnect from worker
- getRelayData() - Get relay connection data
- getRelayStats() - Get relay activity statistics
Import HamburgerMorphing for animated icons
================================================================ */
import {
initNDKPage,
getPubkey, injectHeaderAvatar,
subscribe,
publishEvent,
disconnect,
getVersion,
updateVersionDisplay,
getUserSettings,
patchUserSettings,
onUserSettings
} from './js/init-ndk.mjs';
import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
import { initBlossomSection, updateBlossomSection } from './js/blossom-ui.mjs';
import { initAiSectionWithLocalConfig } from './js/ai-ui.mjs';
// Version will be loaded asynchronously
const versionInfo = await getVersion();
const VERSION = versionInfo.VERSION;
console.log(`[template.html ${VERSION}] Loading...`);
/* ================================================================
GLOBAL VARIABLES
================================================================
Track state for hamburger menu, relay status, and theme.
================================================================ */
let updateIntervalId = null;
let currentPubkey = null;
/*
AUTH STATE MODEL (Template reference)
------------------------------------------------------------------
This template now demonstrates three auth modes for standalone pages:
- required (default):
Behaves like existing pages: login is required immediately.
- optional:
Page can render public/read-only data without login, but can still
prompt login later for user actions (publish, settings, etc).
- none:
Never auto-login on load (pure public page).
URL behavior in this template:
- If ?auth=required|optional|none is present, it wins.
- Otherwise, if URL includes ?npub=... or ?pubkey=..., mode defaults
to optional because pages with explicit profile targets are commonly
public-readable.
- Otherwise, mode defaults to required.
*/
let isAuthenticated = false;
let authMode = 'required';
let authedPageInitialized = false;
let relayActivityListenersBound = false;
// Hamburger menu
let hamburgerInstance = null;
let isNavOpen = false;
// Version bar buttons
let logoutHamburger = null;
let themeToggleHamburger = null;
let isDarkMode = false;
// App-wide user settings (NIP-78 kind 30078, d:user-settings)
let pageSettings = {};
let unsubscribeUserSettings = null;
/* ================================================================
DOM VARIABLES
================================================================
Cache DOM element references for better performance.
================================================================ */
const divBody = document.getElementById("divBody");
const divSideNav = document.getElementById("divSideNav");
const divSideNavBody = document.getElementById("divSideNavBody");
const divFooterCenter = document.getElementById("divFooterCenter");
const divFooterRight = document.getElementById("divFooterRight");
/* ================================================================
HAMBURGER MENU
================================================================
Initialize and control the animated hamburger menu.
================================================================ */
function initHamburgerMenu() {
hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
hamburgerInstance.animateTo('burger');
}
/* ================================================================
SIDENAV FUNCTIONS
================================================================
Open/close sidenav with hamburger morphing animation.
================================================================ */
function openNav() {
divSideNav.style.zIndex = 3;
divSideNav.style.width = "clamp(400px, 50vw, 600px)";
isNavOpen = true;
if (hamburgerInstance) {
hamburgerInstance.animateTo('arrow_left');
}
// Initialize version bar buttons when sidenav opens (lazy load)
if (!logoutHamburger) {
logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
size: 24,
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
logoutHamburger.animateTo('x');
}
if (!themeToggleHamburger) {
themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
size: 24,
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
// Determine current theme
const savedTheme = localStorage.getItem('theme');
isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
const initialShape = isDarkMode ? 'moon' : 'circle';
themeToggleHamburger.animateTo(initialShape);
}
}
function closeNav() {
divSideNav.style.width = "0vw";
divSideNav.style.zIndex = -1;
isNavOpen = false;
if (hamburgerInstance) {
hamburgerInstance.animateTo('burger');
}
}
function toggleNav() {
if (isNavOpen) {
closeNav();
} else {
openNav();
}
}
/* ================================================================
AUTH MODE HELPERS
================================================================
These functions are meant as reusable guidance for future pages.
================================================================ */
function hasTargetPubkeyInUrl() {
const params = new URLSearchParams(window.location.search || '');
const npub = String(params.get('npub') || '').trim();
const pubkey = String(params.get('pubkey') || '').trim();
return Boolean(npub || pubkey);
}
function resolveAuthModeFromUrl() {
const params = new URLSearchParams(window.location.search || '');
const explicitAuth = String(params.get('auth') || '').trim().toLowerCase();
if (explicitAuth === 'required' || explicitAuth === 'optional' || explicitAuth === 'none') {
return explicitAuth;
}
// Convention: explicit target profiles are public-readable by default.
if (hasTargetPubkeyInUrl()) {
return 'optional';
}
return 'required';
}
function isAuthRequiredError(error) {
const message = String(error?.message || error || '').toLowerCase();
return message.includes('authentication required');
}
async function initializeAuthentication(mode) {
// required: existing behavior, throw if auth fails.
if (mode === 'required') {
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
return;
}
// none: public page, no login attempt on load.
if (mode === 'none') {
isAuthenticated = false;
currentPubkey = null;
return;
}
// optional: try silent/normal init; if auth required, continue public.
try {
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
} catch (error) {
if (isAuthRequiredError(error)) {
console.log('[template.html] Optional auth mode: continuing unauthenticated');
isAuthenticated = false;
currentPubkey = null;
return;
}
throw error;
}
}
async function initializeAuthenticatedPageFeatures() {
if (!isAuthenticated || authedPageInitialized) return;
await injectHeaderAvatar(currentPubkey);
console.log('[template.html] Authenticated as:', currentPubkey);
// Hydrate app-wide user settings for this page
try {
pageSettings = await getUserSettings();
} catch (error) {
console.warn('[template.html] getUserSettings failed:', error);
pageSettings = {};
}
// Subscribe to live user settings updates (cross-tab + publish echoes)
if (!unsubscribeUserSettings) {
unsubscribeUserSettings = onUserSettings((settings) => {
pageSettings = settings || {};
// TODO: Re-render page-specific UI from pageSettings here.
});
}
// Initialize relay-dependent UI only once authenticated.
initFooterRelayStatus();
initSidenavRelaySection();
await initBlossomSection();
initAiSectionWithLocalConfig();
await UpdateFooter();
if (!updateIntervalId) {
updateIntervalId = setInterval(UpdateFooter, 1000);
}
// Relay activity listeners only matter after worker init/auth.
if (!relayActivityListenersBound) {
window.addEventListener('ndkRelayActivity', (event) => {
const { relayUrl, activity, stats } = event.detail;
console.log(`[template.html] Relay activity: ${relayUrl} - ${activity}`, stats);
setRelayActivityState(relayUrl, activity);
});
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'relayActivity') {
const { relayUrl, activity } = event.data;
console.log(`[template.html] Relay activity: ${relayUrl} - ${activity}`);
setRelayActivityState(relayUrl, activity);
}
});
relayActivityListenersBound = true;
}
authedPageInitialized = true;
}
async function promptLoginIfNeeded() {
if (isAuthenticated) return true;
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
await initializeAuthenticatedPageFeatures();
return true;
}
/* ================================================================
UPDATE FOOTER
================================================================
Update footer sections with relay status, pubkey, and other info.
Called periodically by update loop.
================================================================ */
const UpdateFooter = async () => {
try {
// Update relay status visuals in footer and sidenav
await updateFooterRelayStatus();
await updateSidenavRelaySection();
await updateBlossomSection();
// Clear center and right sections
divFooterCenter.innerHTML = '';
divFooterRight.innerHTML = '';
} catch (error) {
console.error('[template.html] Error updating footer:', error);
}
};
/* ================================================================
LOGOUT
================================================================
Complete logout process:
1. Stop update loop
2. Disconnect from NDK worker
3. Logout from nostr-login-lite
4. Clear all storage (localStorage, sessionStorage, IndexedDB)
5. Reload page
================================================================ */
const Logout = async () => {
console.log("[template.html] Starting logout process...");
// Stop the update loop
if (updateIntervalId) {
clearInterval(updateIntervalId);
updateIntervalId = null;
}
// Disconnect from worker
disconnect();
// Logout from nostr-login-lite
if (window.NOSTR_LOGIN_LITE && window.NOSTR_LOGIN_LITE.logout) {
await window.NOSTR_LOGIN_LITE.logout();
}
// Clear all storage
localStorage.clear();
sessionStorage.clear();
// Clear IndexedDB
if (window.indexedDB) {
const databases = await window.indexedDB.databases();
for (const db of databases) {
if (db.name) {
window.indexedDB.deleteDatabase(db.name);
}
}
}
console.log("[template.html] Logged out, reloading page");
location.reload(true);
};
/* ================================================================
EVENT LISTENERS
================================================================
Wire up UI interactions.
Main hamburger button click handler is set up in main() after initialization.
================================================================ */
/* ================================================================
SUBSCRIPTION EXAMPLE
================================================================
Example of how to subscribe to Nostr events:
const sub = subscribe(
{ kinds: [1], authors: [pubkey], limit: 10 },
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
);
Cache usage options:
- 'CACHE_FIRST' - Check cache first, then relays
- 'ONLY_RELAY' - Only query relays
- 'ONLY_CACHE' - Only query cache
- 'PARALLEL' - Query cache and relays simultaneously
Listen for events via window events:
window.addEventListener('ndkEvent', (event) => {
const evt = event.detail;
console.log('Received event:', evt);
});
================================================================ */
/* ================================================================
PUBLISH EXAMPLE
================================================================
Example of how to publish a Nostr event:
const event = {
created_at: Math.floor(Date.now() / 1000),
kind: 1,
tags: [],
content: "Hello, Nostr!"
};
try {
const result = await publishEvent(event);
console.log("✅ Published to:", result.relayResults.successful);
console.log("❌ Failed:", result.relayResults.failed);
console.log("Total relays:", result.totalRelays);
} catch (error) {
console.error("Publish error:", error);
}
Note: Events are automatically signed by the NDK worker using
the message-based signer (which calls window.nostr.signEvent).
================================================================ */
/* ================================================================
USER SETTINGS EXAMPLE (NIP-78)
================================================================
Read/subscribe/write helper pattern for all pages:
// Read latest merged settings (cache + relay hydrated by worker)
const settings = await getUserSettings();
// Subscribe to cross-tab updates
const unsubscribe = onUserSettings((nextSettings) => {
// Re-render page from nextSettings
});
// Patch only your feature namespace
await patchUserSettings({
myFeature: {
someFlag: true
}
});
// On page teardown (if applicable)
// unsubscribe();
================================================================ */
/* ================================================================
INITIALIZATION
================================================================
Main initialization sequence:
1. Initialize hamburger menu
2. Set up hamburger click handler
3. Resolve auth mode from URL/query policy
4. Initialize authentication based on mode
5. Initialize authenticated-only features (if signed in)
6. Set up version bar button listeners
7. Restore sidenav state
8. Update version display
Notes:
- required mode = existing behavior (prompt login on load)
- optional mode = allow public load, login later on demand
- none mode = no auto-login on load
================================================================ */
(async function main() {
console.log("[template.html] Starting initialization...");
try {
// Initialize hamburger menu first
initHamburgerMenu();
// Add click handler to hamburger
const divSvgHam = document.getElementById('divSvgHam');
if (divSvgHam) {
divSvgHam.addEventListener('click', toggleNav);
}
// Initialize version bar buttons
const themeToggleButton = document.getElementById('themeToggleButton');
const logoutButton = document.getElementById('logoutButton');
if (themeToggleButton) {
themeToggleButton.addEventListener('click', () => {
isDarkMode = !isDarkMode;
if (isDarkMode) {
localStorage.setItem('theme', 'dark');
} else {
localStorage.setItem('theme', 'light');
}
// Save sidenav state before reload
localStorage.setItem('sidenavWasOpen', isNavOpen ? 'true' : 'false');
window.location.reload();
});
}
if (logoutButton) {
logoutButton.addEventListener('click', async () => {
try {
// In optional/none modes this doubles as a "Sign in" entry point.
if (!isAuthenticated) {
await promptLoginIfNeeded();
return;
}
await Logout();
} catch (error) {
console.error('Logout/login action failed:', error);
}
});
}
// Resolve and initialize page auth policy.
authMode = resolveAuthModeFromUrl();
console.log('[template.html] Resolved auth mode:', authMode);
await initializeAuthentication(authMode);
// Initialize authenticated features only when signed in.
await initializeAuthenticatedPageFeatures();
/* ============================================================
EXAMPLE: Subscribe to events
============================================================
Uncomment to subscribe to user's notes:
const notesSub = subscribe(
{ kinds: [1], authors: [currentPubkey], limit: 10 },
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
);
console.log("[template.html] Subscribed to kind 1");
============================================================ */
/* ============================================================
EXAMPLE: Listen for events from worker
============================================================
Uncomment to handle incoming events:
window.addEventListener('ndkEvent', (event) => {
const evt = event.detail;
console.log("[template.html] Received event:", evt.kind, evt.pubkey);
if (evt.pubkey === currentPubkey) {
if (evt.kind === 1) {
// Handle note event
console.log("Note:", evt.content);
}
}
});
============================================================ */
/* ============================================================
EXAMPLE: Listen for cached profile
============================================================
Uncomment to handle cached profile data:
window.addEventListener('ndkProfile', (event) => {
console.log("[template.html] Cached profile:", event.detail);
// event.detail contains profile object (name, about, etc.)
});
============================================================ */
// Restore sidenav state if it was open before theme toggle
const sidenavWasOpen = localStorage.getItem('sidenavWasOpen');
if (sidenavWasOpen === 'true') {
localStorage.removeItem('sidenavWasOpen');
openNav();
}
// Optional UX note for public mode pages.
if (!isAuthenticated && (authMode === 'optional' || authMode === 'none')) {
divFooterCenter.textContent = 'Public mode';
divFooterRight.textContent = 'Sign in from side menu for private features';
}
// Update version display
await updateVersionDisplay();
console.log('[template.html] Initialization complete');
} catch (error) {
console.error('[template.html] Initialization failed:', error);
divBody.innerHTML = `<div style="text-align: center; padding: 50px;">
<div style="font-size: 24px; margin-bottom: 20px; color: red;">❌ Authentication Error</div>
<div style="font-size: 16px; color: #666;">${error.message}</div>
<div style="margin-top: 20px;">
<button onclick="location.reload()" style="padding: 10px 20px; font-size: 16px;">Retry</button>
</div>
</div>`;
}
})();
/* ================================================================
WORKER MESSAGE TYPES
================================================================
The NDK worker can send these message types:
1. 'response' - Response to init/subscribe/publish requests
- data.profile - User profile (from init)
- data.relays - User relays (from init)
- data.success - Publish success status
- data.relayResults - Relay publish results
2. 'event' - Nostr event from subscription
- Dispatched as 'ndkEvent' window event
- event.detail contains the Nostr event
3. 'eose' - End of stored events for subscription
- Dispatched as 'ndkEose' window event
- event.detail.subId contains subscription ID
4. 'signRequest' - Request to sign event/encrypt/decrypt
- Handled automatically by init-ndk.mjs
- Calls window.nostr methods and sends response
5. 'error' - Error from worker
- Logged to console automatically
6. 'relayActivity' - Relay read/write activity notification
- Dispatched as 'ndkRelayActivity' window event
- Used to animate relay status icons in footer
================================================================ */
/* ================================================================
DISTRIBUTED ARCHITECTURE NOTES
================================================================
Each page is independently accessible and self-contained:
1. Authentication persists via nostr-login-lite localStorage
- Login once on any page
- All other pages automatically authenticated
2. NDK SharedWorker is shared across all tabs/pages
- Single NDK instance manages all connections
- Subscriptions from all pages handled by one worker
- Events broadcast to all connected pages
3. Dexie cache is shared across all pages
- IndexedDB persists across sessions
- Cache-first queries are fast
- Reduces relay load
4. User settings are centralized and shared
- Worker hydrates kind 30078 (`d:user-settings`) on init
- Pages read via getUserSettings()
- Pages patch via patchUserSettings({ featureNamespace: ... })
- Pages subscribe via onUserSettings() for live updates
5. Each page can be distributed independently
- Copy template.html and customize
- No dependencies on other pages
- Works standalone or as part of suite
6. Message-based signer bridges worker and page
- Worker's NDK uses MessageBasedSigner
- Signer sends sign requests to page
- Page calls window.nostr.signEvent()
- Response sent back to worker
- NDK completes signing and publishing
7. Relay status visualization
- Footer left section shows connected relays
- Each relay has animated icon (HamburgerMorphing)
- Icons morph based on activity (read/write)
- Temporary animations show real-time activity
================================================================ */
</script>
</body>
</html>
+47 -68
View File
@@ -1,7 +1,8 @@
#!/bin/bash
# Build fully static MUSL binaries for C-Relay using Alpine Docker
# Build fully static MUSL binaries for C-Relay-PG using Alpine Docker
# Produces truly portable binaries with zero runtime dependencies
# PostgreSQL backend only.
set -e
@@ -11,19 +12,34 @@ DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
# Parse command line arguments
DEBUG_BUILD=false
if [[ "$1" == "--debug" ]]; then
DEBUG_BUILD=true
while [[ $# -gt 0 ]]; do
case "$1" in
--debug)
DEBUG_BUILD=true
shift
;;
*)
echo "ERROR: Unknown argument: $1"
echo "Usage: $0 [--debug]"
exit 1
;;
esac
done
if [[ "$DEBUG_BUILD" == "true" ]]; then
echo "=========================================="
echo "C-Relay MUSL Static Binary Builder (DEBUG MODE)"
echo "C-Relay-PG MUSL Static Binary Builder (DEBUG MODE)"
echo "=========================================="
else
echo "=========================================="
echo "C-Relay MUSL Static Binary Builder (PRODUCTION MODE)"
echo "C-Relay-PG MUSL Static Binary Builder (PRODUCTION MODE)"
echo "=========================================="
fi
echo "Project directory: $SCRIPT_DIR"
echo "Build directory: $BUILD_DIR"
echo "Debug build: $DEBUG_BUILD"
echo "DB backend: postgres"
echo ""
# Create build directory
@@ -63,20 +79,25 @@ ARCH=$(uname -m)
case "$ARCH" in
x86_64)
PLATFORM="linux/amd64"
OUTPUT_NAME="c_relay_static_x86_64"
OUTPUT_NAME="c_relay_pg_static_x86_64"
;;
aarch64|arm64)
PLATFORM="linux/arm64"
OUTPUT_NAME="c_relay_static_arm64"
OUTPUT_NAME="c_relay_pg_static_arm64"
;;
*)
echo "WARNING: Unknown architecture: $ARCH"
echo "Defaulting to linux/amd64"
PLATFORM="linux/amd64"
OUTPUT_NAME="c_relay_static_${ARCH}"
OUTPUT_NAME="c_relay_pg_static_${ARCH}"
;;
esac
# Append _debug suffix to output name for debug builds so production binary is never overwritten
if [ "$DEBUG_BUILD" = true ]; then
OUTPUT_NAME="${OUTPUT_NAME}_debug"
fi
echo "Building for platform: $PLATFORM"
echo "Output binary: $OUTPUT_NAME"
echo ""
@@ -111,14 +132,14 @@ echo "=========================================="
echo "This will:"
echo " - Use Alpine Linux (native MUSL)"
echo " - Build all dependencies statically"
echo " - Compile c-relay with full static linking"
echo " - Compile c-relay-pg with full static linking"
echo ""
$DOCKER_CMD build \
--platform "$PLATFORM" \
--build-arg DEBUG_BUILD=$DEBUG_BUILD \
-f "$DOCKERFILE" \
-t c-relay-musl-builder:latest \
-t c-relay-pg-musl-builder:latest \
--progress=plain \
. || {
echo ""
@@ -142,14 +163,14 @@ $DOCKER_CMD build \
--build-arg DEBUG_BUILD=$DEBUG_BUILD \
--target builder \
-f "$DOCKERFILE" \
-t c-relay-static-builder-stage:latest \
-t c-relay-pg-static-builder-stage:latest \
. > /dev/null 2>&1
# Create a temporary container to copy the binary
CONTAINER_ID=$($DOCKER_CMD create c-relay-static-builder-stage:latest)
CONTAINER_ID=$($DOCKER_CMD create c-relay-pg-static-builder-stage:latest)
# Copy binary from container
$DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_static" "$BUILD_DIR/$OUTPUT_NAME" || {
$DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_pg_static" "$BUILD_DIR/$OUTPUT_NAME" || {
echo "ERROR: Failed to extract binary from container"
$DOCKER_CMD rm "$CONTAINER_ID" 2>/dev/null
exit 1
@@ -158,73 +179,31 @@ $DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_static" "$BUILD_DIR/$OUTPUT_NAME" |
# Clean up container
$DOCKER_CMD rm "$CONTAINER_ID" > /dev/null
echo "✓ Binary extracted to: $BUILD_DIR/$OUTPUT_NAME"
echo "✓ Binary extracted: $BUILD_DIR/$OUTPUT_NAME"
echo ""
# Make binary executable
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
# Verify the binary
echo "=========================================="
echo "Step 3: Verifying static binary"
echo "=========================================="
echo ""
file "$BUILD_DIR/$OUTPUT_NAME"
ls -lh "$BUILD_DIR/$OUTPUT_NAME"
echo "Checking for dynamic dependencies:"
if LDD_OUTPUT=$(timeout 5 ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable"; then
echo "✓ Binary is fully static (no dynamic dependencies)"
TRULY_STATIC=true
elif echo "$LDD_OUTPUT" | grep -q "statically linked"; then
echo "✓ Binary is statically linked"
TRULY_STATIC=true
else
echo "⚠ WARNING: Binary may have dynamic dependencies:"
echo "$LDD_OUTPUT"
TRULY_STATIC=false
fi
# Check for dynamic dependencies
if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then
echo "✓ Binary is truly static (no dynamic dependencies)"
else
# ldd failed or timed out - check with file command instead
if file "$BUILD_DIR/$OUTPUT_NAME" | grep -q "statically linked"; then
echo "✓ Binary is statically linked (verified with file command)"
TRULY_STATIC=true
else
echo "⚠ Could not verify static linking (ldd check failed)"
TRULY_STATIC=false
fi
echo "⚠ Binary has dynamic dependencies:"
ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1
fi
echo ""
echo "File size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')"
echo ""
# Test if binary runs
echo "Testing binary execution:"
if "$BUILD_DIR/$OUTPUT_NAME" --version 2>&1 | head -5; then
echo "✓ Binary executes successfully"
else
echo "⚠ Binary execution test failed (this may be normal if --version is not supported)"
fi
echo ""
# Summary
echo "=========================================="
echo "Build Summary"
echo "Build complete!"
echo "=========================================="
echo "Binary: $BUILD_DIR/$OUTPUT_NAME"
echo "Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)"
echo "Platform: $PLATFORM"
if [ "$DEBUG_BUILD" = true ]; then
echo "Build Type: DEBUG (with symbols, no optimization)"
else
echo "Build Type: PRODUCTION (optimized, stripped)"
fi
if [ "$TRULY_STATIC" = true ]; then
echo "Linkage: Fully static binary (Alpine MUSL-based)"
echo "Portability: Works on ANY Linux distribution"
else
echo "Linkage: Static binary (may have minimal dependencies)"
fi
echo ""
echo "✓ Build complete!"
echo "Size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')"
echo ""
# Clean up Docker builder stage image
$DOCKER_CMD rmi c-relay-pg-static-builder-stage:latest > /dev/null 2>&1 || true
+31
View File
@@ -0,0 +1,31 @@
# Database files (from c-relay running in this directory)
*.db
*.db-shm
*.db-wal
# Build artifacts
*.o
*.a
# Binary (built into root, not version-controlled)
caching_relay
caching_relay_static_*
# Logs
*.log
crelay.log
# Test configs (not for version control)
test_config*.jsonc
# Note: caching_relay_config.jsonc IS version-controlled (it's the default
# config + state store). Don't ignore it.
# Docker build context temp (copied nostr_core_lib during static build)
nostr_core_lib/
# Editor
*.swp
*.swo
*~
.vscode/
+128
View File
@@ -0,0 +1,128 @@
# Alpine-based MUSL static binary builder for caching_relay
# Produces a truly portable binary with zero runtime dependencies.
# Adapted from c-relay's Dockerfile.alpine-musl.
ARG DEBUG_BUILD=false
FROM alpine:3.19 AS builder
ARG DEBUG_BUILD=false
# Install build dependencies
RUN apk add --no-cache \
build-base \
musl-dev \
git \
cmake \
pkgconfig \
autoconf \
automake \
libtool \
openssl-dev \
openssl-libs-static \
zlib-dev \
zlib-static \
curl-dev \
curl-static \
sqlite-dev \
postgresql-dev \
linux-headers \
wget \
bash
WORKDIR /build
# Build libsecp256k1 static
RUN cd /tmp && \
git clone https://github.com/bitcoin-core/secp256k1.git && \
cd secp256k1 && \
./autogen.sh && \
./configure --enable-static --disable-shared --prefix=/usr \
CFLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/secp256k1
# Build libwebsockets static with minimal features
RUN cd /tmp && \
git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \
cd libwebsockets && \
mkdir build && cd build && \
cmake .. \
-DLWS_WITH_STATIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_WITH_SSL=ON \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITHOUT_TEST_SERVER=ON \
-DLWS_WITHOUT_TEST_CLIENT=ON \
-DLWS_WITHOUT_TEST_PING=ON \
-DLWS_WITH_HTTP2=OFF \
-DLWS_WITH_LIBUV=OFF \
-DLWS_WITH_LIBEVENT=OFF \
-DLWS_IPV6=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_FLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/libwebsockets
# Copy nostr_core_lib source (sibling project)
COPY nostr_core_lib /build/nostr_core_lib/
# Build nostr_core_lib with the NIPs needed by the relay pool + signer:
# 1 (basic), 6 (keys), 19 (npub bech32), 4 (legacy encryption, signer dep),
# 42 (auth, relay pool dep), 44 (modern encryption, signer dep)
RUN cd nostr_core_lib && \
chmod +x build.sh && \
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
rm -f *.o *.a 2>/dev/null || true && \
./build.sh --nips=1,4,6,19,42,44 && \
if [ -f libnostr_core_arm64.a ]; then \
cp libnostr_core_arm64.a libnostr_core.a; \
elif [ -f libnostr_core_x64.a ]; then \
cp libnostr_core_x64.a libnostr_core.a; \
else \
echo "ERROR: No supported nostr_core static library produced"; \
ls -la *.a 2>/dev/null || true; \
exit 1; \
fi
# Copy caching_relay source LAST (only this layer rebuilds on source changes)
COPY src/ /build/src/
# Build caching_relay with full static linking.
# No sqlite (the daemon doesn't use it), no c_utils (not needed).
RUN if [ "$DEBUG_BUILD" = "true" ]; then \
CFLAGS="-g -O2 -DDEBUG -fno-omit-frame-pointer"; \
STRIP_CMD="echo 'Keeping debug symbols'"; \
else \
CFLAGS="-O2"; \
STRIP_CMD="strip /build/caching_relay_static"; \
fi && \
gcc -static $CFLAGS -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Isrc -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
-I/usr/include/postgresql \
src/main.c src/debug.c src/jsonc_strip.c src/config.c src/state.c \
src/follow_graph.c src/relay_sink.c src/live_subscriber.c \
src/backfill.c src/relay_discovery.c \
src/pg_inbox.c src/pg_config.c src/forward_catchup.c \
-o /build/caching_relay_static \
nostr_core_lib/libnostr_core.a \
-lwebsockets -lssl -lcrypto -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl -lpq -lpgcommon -lpgport && \
eval "$STRIP_CMD"
# Verify it's truly static
RUN echo "=== Binary Information ===" && \
file /build/caching_relay_static && \
ls -lh /build/caching_relay_static && \
echo "=== Checking for dynamic dependencies ===" && \
(ldd /build/caching_relay_static 2>&1 || echo "Binary is static") && \
echo "=== Build complete ==="
# Output stage - just the binary
FROM scratch AS output
COPY --from=builder /build/caching_relay_static /caching_relay_static
+85
View File
@@ -0,0 +1,85 @@
# caching_relay Makefile - statically linked C99 binary, c-relay style
# Use bash for reliable glob expansion in recipes (dash handles globs differently).
SHELL := /bin/bash
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -g -O2
# nostr_core_lib is inside the c-relay-pg project root (one dir up from caching/).
NOSTR_CORE_DIR = ../nostr_core_lib
INCLUDES = -I. -Isrc -I$(NOSTR_CORE_DIR) -I$(NOSTR_CORE_DIR)/nostr_core \
-I$(NOSTR_CORE_DIR)/cjson -I$(NOSTR_CORE_DIR)/nostr_websocket \
-I/usr/include/postgresql
# -lsqlite3: nostr_core_lib's request_validator.x64.o references SQLite symbols;
# we use --whole-archive so the linker pulls in all objects (needed for NIP-42
# cross-references within the archive), which means SQLite must be satisfied.
# No c_utils_lib: nostr_core_lib does not depend on it for the NIPs we use.
# -lpq: PostgreSQL client library for the caching_event_inbox integration.
LIBS = -lwebsockets -lssl -lcrypto -lsecp256k1 -lcurl -lz -ldl -lpthread -lm -lpq -lsqlite3
MAIN_SRC = src/main.c src/debug.c src/jsonc_strip.c src/config.c src/state.c \
src/follow_graph.c src/relay_sink.c src/live_subscriber.c \
src/backfill.c src/forward_catchup.c src/relay_discovery.c \
src/pg_inbox.c src/pg_config.c
# Architecture detection
ARCH = $(shell uname -m)
ifeq ($(ARCH),x86_64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_x64.a
else ifeq ($(ARCH),aarch64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_arm64.a
else ifeq ($(ARCH),arm64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_arm64.a
else
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_x64.a
endif
# Binary goes in the c-relay-pg build/ directory so the launcher can find it.
TARGET = ../build/caching_relay
all: $(TARGET)
# Build nostr_core_lib with the NIPs needed by the relay pool + signer:
# 1 (basic), 6 (keys), 19 (npub bech32), 4 (legacy encryption, signer dep),
# 42 (auth, relay pool dep), 44 (modern encryption, signer dep)
$(NOSTR_CORE_LIB):
@echo "Building nostr_core_lib with required NIPs..."
cd $(NOSTR_CORE_DIR) && ./build.sh --nips=1,4,6,19,42,44
$(TARGET): $(MAIN_SRC) $(NOSTR_CORE_LIB)
@echo "Compiling caching_relay for architecture: $(ARCH)"
@# Extract all objects from the static library and link them directly.
@# This avoids archive symbol resolution ordering issues (NIP-42 cross-refs).
@# Use a fixed temp dir and chain all commands with && so failures stop the build.
@rm -rf /tmp/cr_lib && mkdir -p /tmp/cr_lib && \
ar x $(NOSTR_CORE_LIB) --output=/tmp/cr_lib && \
echo "Extracted $$(ls /tmp/cr_lib/*.o | wc -l) objects" && \
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) /tmp/cr_lib/*.o -o $(TARGET) $(LIBS) && \
rm -rf /tmp/cr_lib && \
echo "Build complete: $(TARGET)"
# ------------------------------------------------------------------ #
# cache_all_test - feasibility test for "cache everything" approach #
# ------------------------------------------------------------------ #
TEST_TARGET = ../build/cache_all_test
TEST_SRC = src/cache_all_test.c src/debug.c src/jsonc_strip.c src/config.c \
src/state.c
$(TEST_TARGET): $(TEST_SRC) $(NOSTR_CORE_LIB)
@echo "Compiling cache_all_test for architecture: $(ARCH)"
@rm -rf /tmp/cr_lib && mkdir -p /tmp/cr_lib && \
ar x $(NOSTR_CORE_LIB) --output=/tmp/cr_lib && \
echo "Extracted $$(ls /tmp/cr_lib/*.o | wc -l) objects" && \
$(CC) $(CFLAGS) $(INCLUDES) $(TEST_SRC) /tmp/cr_lib/*.o -o $(TEST_TARGET) $(LIBS) && \
rm -rf /tmp/cr_lib && \
echo "Build complete: $(TEST_TARGET)"
cache_all_test: $(TEST_TARGET)
clean:
rm -f $(TARGET) $(TEST_TARGET)
.PHONY: all clean cache_all_test
+297
View File
@@ -0,0 +1,297 @@
# caching_relay
A C99 daemon that caches Nostr events from people you follow into a local relay,
so your Nostr client can point at a single fast local relay instead of fanning
out to dozens of upstream relays.
## What it does
1. Reads a `.jsonc` config file listing your **root npub(s)**, upstream relays,
a local relay URL, and the event kinds to cache.
2. For each root npub, fetches its kind-3 contact list to discover **followed
pubkeys** (your follows + their follows).
3. **Live-subscribes** to new events of the configured kinds from the union of
followed pubkeys.
4. **Backfills** historical events using a progressive window-expansion strategy
(24h → 7d → 30d → 90d → 365d), round-robin per pubkey, throttled to be polite
to upstream relays.
5. **Re-publishes** every fetched event to the local relay via a plain WebSocket
`EVENT` client connection (relay-agnostic - works with c-relay, c-relay-pg,
or any Nostr relay).
6. Persists its backfill progress **in the config file itself** so a restart
resumes where it left off.
## Build
### Static binary (recommended, c-relay style)
Produces a truly portable statically-linked MUSL binary with zero runtime
dependencies:
```bash
./build_static.sh
# or with debug symbols:
./build_static.sh --debug
# or cross-compile for arm64:
./build_static.sh --arch arm64
```
Output: `caching_relay` (in the project root).
Requires Docker. The build runs in an Alpine container that compiles
libsecp256k1, libwebsockets, and nostr_core_lib from source, then statically
links everything.
### Local build (if you have the shared libs installed)
```bash
make
```
Output: `caching_relay` (in the project root).
Requires: `libwebsockets`, `openssl`, `libsecp256k1`, `libcurl`, `zlib` shared
libraries, and a pre-built `../nostr_core_lib/libnostr_core_x64.a`.
## Usage
```bash
./caching_relay -d 3
```
The daemon looks for `caching_relay_config.jsonc` in the current directory by default.
You can override with `-c`:
```bash
./caching_relay -c /path/to/my_config.jsonc -d 3
```
Options:
- `-c, --config <file>` - Path to `.jsonc` config file (default: `./caching_relay_config.jsonc`)
- `-d, --debug <level>` - Log level 1-4 (1=error, 2=warn, 3=info, 4=debug). Default 3.
- `-h, --help` - Show help
Signals:
- `SIGINT` / `SIGTERM` - Graceful shutdown (saves state, closes connections)
- `SIGHUP` - Reload config (preserves backfill state)
## Config file
The config file [`caching_relay_config.jsonc`](caching_relay_config.jsonc) lives in the
project root. It is JSONC (JSON with `//` and `/* */` comments). The daemon
rewrites it as plain JSON when saving state (comments are not preserved on
rewrite).
Key fields:
| Field | Description |
|-------|-------------|
| `root_npubs` | npubs whose kind-3 follows list we crawl |
| `upstream_relays` | bootstrap relays for initial discovery (kind-3, kind-10002). Outbox relays are discovered dynamically via NIP-65. |
| `local_relay` | relay to publish cached events into (never queried) |
| `kinds` | event kinds to cache for followed people (e.g. `[1, 3, 6, 10000, 30023]`) |
| `admin_kinds` | kinds to follow specifically for root (admin) npubs. Use `["*"]` for all kinds. If omitted, admin uses same `kinds` as everyone else. |
| `backfill.window_schedule_seconds` | progressive window sizes in seconds |
| `backfill.events_per_tick` | max events per pubkey per backfill tick |
| `backfill.tick_interval_seconds` | delay between pubkey backfills (throttle) |
| `live.enabled` | enable live subscription |
| `follow_graph_refresh_seconds` | how often to re-resolve the follow graph |
| `state.*` | managed by the daemon - do not hand-edit |
## Architecture
See [`plans/plan.md`](plans/plan.md) for the full architecture document with
Mermaid diagrams.
The daemon uses a **NIP-65 outbox model**: instead of querying a fixed set of
bootstrap relays for everything, it discovers which relays each followed pubkey
actually posts to (via kind 10002 relay lists) and connects to those relays
dynamically. The `upstream_relays` in the config serve only as **bootstrap
relays** -- the initial set used to discover kind-3 and kind-10002 events before
the outbox relay map is built.
### Relay pool selection: minimum covering set
After discovering each followed pubkey's outbox relays (from their kind 10002),
the daemon computes the **minimum set of relays that covers all followed
pubkeys**. This is the classic set cover problem, solved with a greedy
approximation:
1. Build a map: `{relay_url -> set of pubkeys that list it in their 10002}`
2. Greedily pick the relay that covers the most uncovered pubkeys
3. Repeat until all pubkeys are covered (or no more relays to pick)
4. Always include the bootstrap relays in the final set (they may have events
from pubkeys that don't publish a kind 10002)
This minimizes the number of WebSocket connections while ensuring every
followed pubkey is reachable. The daemon logs the selected relay set and which
pubkeys each relay covers.
Pubkeys that have no kind 10002 (or whose 10002 lists no relays) are covered by
the bootstrap relays as a fallback.
```
┌─────────────────────────────────────────┐
│ caching_relay │
│ │
upstream relays │ upstream_pool sink_pool │ local relay
(damus, nos.lol) │ (query + subscribe) (publish only) │ (c-relay)
│ │ │ │ │ │
│ │ follow_graph relay_sink │ │
└──────────►│ live_subscriber ──────►│ ├─────►│
│ backfill ──────────────►│ │ │
│ │ │ │ │
│ state + seen ring │ │ │
└──────────────────────────────────────────┘ │
```
Two `nostr_relay_pool_t` instances:
- **upstream_pool** - holds bootstrap relays initially, then dynamically adds
outbox relays discovered from kind 10002. Used for `query_sync` (kind-3 fetch,
kind-10002 fetch, backfill) and the long-lived live subscription.
- **sink_pool** - holds only the local relay; used exclusively for
`publish_async`. Never queried.
## Startup Flow
### First-time startup (empty local relay)
The daemon has never run before. The local relay has no cached events. The
daemon must bootstrap from the config's `upstream_relays` to discover the
outbox relays for each followed pubkey.
```
START
|
v
Load config (caching_relay_config.jsonc)
| - root_npubs, upstream_relays (bootstrap), kinds, admin_kinds
| - state.backfilled_until == 0 => first-time startup
|
v
Create upstream_pool with bootstrap relays only
Create sink_pool with local_relay
|
v
Phase 1: Resolve follow graph (from bootstrap relays)
| - For each root npub: query_sync kind=3 from bootstrap relays
| - Parse "p" tags => followed pubkey set
| - Log: "follow: resolved N followed pubkeys"
|
v
Phase 2: Discover outbox relays (NIP-65, kind 10002) from bootstrap relays
| - For each followed pubkey: query_sync kind=10002 from bootstrap relays
| - Parse "r" tags => per-pubkey relay list
| - Publish all kind-10002 events to local relay (cache them)
| - Dynamically add discovered relays to upstream_pool
| - Log: "relay_discovery: found N relays from M pubkeys"
| - Log: "upstream_pool: now connected to N relays" + list them
|
v
Phase 3: Open live subscriptions on upstream_pool
| - follows_sub: non-admin pubkeys + regular kinds
| - admin_sub: admin npubs + admin_kinds (or all kinds)
|
v
Phase 4: Begin progressive backfill
| - Window 0: 24h -> query each pubkey from their outbox relays
| - Window 1: 7d -> ...
| - Window 2: 30d -> ...
| - Publish all fetched events to local relay
| - Save state to config file as each window completes
|
v
Steady-state: live sub + periodic follow-graph refresh + periodic
kind-10002 refresh + backfill re-cycle
|
v
SHUTDOWN (SIGINT/SIGTERM) -> save state -> clean exit
```
### Subsequent startup (local relay already has cached events)
The daemon has run before. The local relay already has kind-3 and kind-10002
events cached from the previous run. The daemon can read these from the local
relay directly (fast, no network round-trip to bootstrap relays) and only falls
back to bootstrap relays for pubkeys it cannot find locally.
```
START
|
v
Load config (caching_relay_config.jsonc)
| - state.backfilled_until > 0 => subsequent startup
| - state.current_window_index, backfill_cursor preserved
|
v
Create upstream_pool with bootstrap relays
Create sink_pool with local_relay
|
v
Phase 1: Resolve follow graph (from LOCAL relay first, bootstrap fallback)
| - Query local relay for kind=3 per root npub
| - If found locally: use it (fast, no upstream query)
| - If not found: fall back to bootstrap relays
| - Parse "p" tags => followed pubkey set
| - Log: "follow: resolved N followed pubkeys (L local, B bootstrap)"
|
v
Phase 2: Discover outbox relays (from LOCAL relay first, bootstrap fallback)
| - Query local relay for kind=10002 per followed pubkey
| - If found locally: use it (fast)
| - If not found: fall back to bootstrap relays, cache result to local
| - Parse "r" tags => per-pubkey relay list
| - Dynamically add discovered relays to upstream_pool
| - Log: "relay_discovery: found N relays (L local, B bootstrap)"
| - Log: "upstream_pool: now connected to N relays" + list them
|
v
Phase 3: Open live subscriptions on upstream_pool
| - (same as first-time)
|
v
Phase 4: Resume backfill from saved state
| - Resume at state.current_window_index, state.backfill_cursor
| - No re-pull of already-backfilled windows
| - Continue progressive window expansion from where it left off
|
v
Steady-state (same as first-time)
|
v
SHUTDOWN -> save state -> clean exit
```
### Relay logging
The daemon logs all relay activity so you can see exactly which relays are
being used:
- `upstream: added wss://relay.damus.io` -- each relay added to the pool
- `relay_discovery: found 47 relays from 195 pubkeys` -- outbox discovery summary
- `upstream_pool: 50 relays connected:` -- full relay list at startup
- `relay_discovery: pubkey X -> wss://relay.example.com (local)` -- per-pubkey
relay source (local cache vs bootstrap)
- `backfill: pubkey[3/195] abc123 -> wss://relay.example.com (12 events)` --
which relay served each backfill query
## Dependencies
- [nostr_core_lib](../nostr_core_lib) - built with NIPs 1, 4, 6, 19, 42, 44
- libwebsockets, openssl, libsecp256k1, libcurl, zlib (all statically linked
in the Docker build)
## Testing
Start a local c-relay, then run the daemon:
```bash
# Start local relay (from c-relay project)
cd ../c-relay/build && ./c_relay_static_x86_64 -p 8888 &
# Run the caching daemon (uses ./caching_relay_config.jsonc by default)
./caching_relay -d 3
# Verify events are cached (from another terminal)
nak req -k 1 -l 10 ws://127.0.0.1:8888
```
+1
View File
@@ -0,0 +1 @@
0.0.2
+147
View File
@@ -0,0 +1,147 @@
#!/bin/bash
# Build fully static MUSL binary for caching_relay using Alpine Docker.
# Produces a truly portable binary with zero runtime dependencies.
# Adapted from c-relay's build_static.sh.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
# Parse command line arguments
DEBUG_BUILD=false
TARGET_ARCH=""
while [[ $# -gt 0 ]]; do
case "$1" in
--debug)
DEBUG_BUILD=true
shift
;;
--arch)
if [[ -z "$2" ]]; then
echo "ERROR: --arch requires a value"
echo "Usage: $0 [--debug] [--arch <arm64|x86_64>]"
exit 1
fi
case "$2" in
arm64|x86_64)
TARGET_ARCH="$2"
;;
*)
echo "ERROR: Unsupported architecture '$2'"
echo "Supported values: arm64, x86_64"
exit 1
;;
esac
shift 2
;;
*)
echo "ERROR: Unknown argument '$1'"
echo "Usage: $0 [--debug] [--arch <arm64|x86_64>]"
exit 1
;;
esac
done
if [ "$DEBUG_BUILD" = true ]; then
echo "=========================================="
echo "caching_relay MUSL Static Binary Builder (DEBUG MODE)"
echo "=========================================="
else
echo "=========================================="
echo "caching_relay MUSL Static Binary Builder (PRODUCTION MODE)"
echo "=========================================="
fi
echo "Project directory: $SCRIPT_DIR"
echo ""
if ! command -v docker &> /dev/null; then
echo "ERROR: Docker is not installed or not in PATH"
exit 1
fi
if ! docker info &> /dev/null; then
echo "ERROR: Docker daemon is not running or user not in docker group"
exit 1
fi
echo "Docker is available and running"
echo ""
# Detect host architecture
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="x86_64";;
aarch64) ARCH="arm64";;
arm64) ARCH="arm64";;
esac
if [[ -n "$TARGET_ARCH" ]]; then
ARCH="$TARGET_ARCH"
echo "Using target architecture: $ARCH"
else
echo "Detected host architecture: $ARCH"
fi
echo ""
# nostr_core_lib is inside the c-relay-pg project root (one dir up from caching/).
# Copy it into the Docker build context (excluding .git and build artifacts).
NOSTR_CORE_DIR="$SCRIPT_DIR/../nostr_core_lib"
if [ ! -d "$NOSTR_CORE_DIR" ]; then
echo "ERROR: nostr_core_lib not found at $NOSTR_CORE_DIR"
exit 1
fi
NEEDS_COPY=1
if [ -d "$SCRIPT_DIR/nostr_core_lib" ] && [ ! -L "$SCRIPT_DIR/nostr_core_lib" ]; then
# Already a real directory (e.g. from a previous run); assume it's good.
NEEDS_COPY=0
fi
if [ "$NEEDS_COPY" = "1" ]; then
echo "Copying nostr_core_lib into build context..."
rm -rf "$SCRIPT_DIR/nostr_core_lib"
mkdir -p "$SCRIPT_DIR/nostr_core_lib"
rsync -a --exclude='.git' --exclude='*.a' --exclude='*.o' \
--exclude='.venv*' --exclude='backups' --exclude='verify_*' \
--exclude='rewrite_mirror' --exclude='websocket_debug' \
"$NOSTR_CORE_DIR/" "$SCRIPT_DIR/nostr_core_lib/"
COPIED_NOSTR_CORE=1
fi
# Build args
BUILD_ARGS="--build-arg DEBUG_BUILD=$DEBUG_BUILD"
if [ "$ARCH" = "arm64" ] && [ "$(uname -m)" != "aarch64" ] && [ "$(uname -m)" != "arm64" ]; then
BUILD_ARGS="$BUILD_ARGS --platform linux/arm64"
fi
IMAGE_TAG="caching_relay_builder:latest"
echo "Building Docker image..."
docker build $BUILD_ARGS \
-f "$DOCKERFILE" \
-t "$IMAGE_TAG" \
"$SCRIPT_DIR" 2>&1
BUILD_RC=$?
# Clean up the copied nostr_core_lib to keep the project dir tidy.
if [ "${COPIED_NOSTR_CORE:-0}" = "1" ]; then
echo "Cleaning up copied nostr_core_lib from build context..."
rm -rf "$SCRIPT_DIR/nostr_core_lib"
fi
if [ $BUILD_RC -ne 0 ]; then
exit $BUILD_RC
fi
echo ""
echo "Extracting binary from image..."
# The output stage is FROM scratch with no CMD, so pass an empty command.
CONTAINER_ID=$(docker create "$IMAGE_TAG" "")
docker cp "$CONTAINER_ID:/caching_relay_static" "$SCRIPT_DIR/caching_relay"
docker rm "$CONTAINER_ID" >/dev/null
chmod +x "$SCRIPT_DIR/caching_relay"
echo ""
echo "=== Build complete ==="
ls -lh "$SCRIPT_DIR/caching_relay"
file "$SCRIPT_DIR/caching_relay"
+56
View File
@@ -0,0 +1,56 @@
{
// Cache-All Feasibility Test Config
// Root npubs to resolve follow graph from
"root_npubs": [
"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
],
// Relays to test (from server relay list, connected ones only)
"upstream_relays": [
"wss://laantungir.net",
"wss://nos.lol",
"wss://nostr-pub.wellorder.net",
"wss://nostr.mom",
"wss://nostr.oxtr.dev",
"wss://offchain.pub",
"wss://premium.primal.net",
"wss://relay.damus.io",
"wss://relay.ditto.pub",
"wss://relay.divine.video",
"wss://relay.momostr.pink",
"wss://relay.mostr.pub",
"wss://relay.nostrplebs.com",
"wss://relay.primal.net",
"wss://theforest.nostr1.com"
],
// Local relay (not used by test, but required by config parser)
"local_relay": "ws://localhost:8888",
// Kinds to follow (not used by test, but required by config parser)
"kinds": [0, 1, 3, 5, 6, 7, 9735, 10002, 30023],
// Admin kinds
"admin_kinds": ["*"],
// Backfill config (not used by test)
"backfill": {
"enabled": false,
"events_per_tick": 500,
"tick_interval_seconds": 5
},
// Live config (not used by test)
"live": {
"enabled": false,
"resubscribe_interval_seconds": 300
},
// Follow graph refresh (not used by test)
"follow_graph_refresh_seconds": 600,
// State (not used by test)
"state": {
"backfilled_until": 0
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"root_npubs": [
"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
],
"upstream_relays": [
"wss://relay.primal.net"
],
"local_relay": "ws://localhost:8888",
"kinds": [0, 1, 3, 5, 6, 7, 9735, 10002, 30023],
"admin_kinds": ["*"],
"backfill": { "enabled": false, "events_per_tick": 500, "tick_interval_seconds": 5 },
"live": { "enabled": false, "resubscribe_interval_seconds": 300 },
"follow_graph_refresh_seconds": 600,
"state": { "backfilled_until": 0 }
}
@@ -0,0 +1,253 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 5m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 11200
Total bytes: 17099684 (16.31 MB)
Avg event size: 1527 bytes
Max event: 45848 bytes (kind 3)
Unique pubkeys: 4292
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 3492 1 0 0 0 0 DOWN
wss://nos.lol 5613 0 0 0 0 0 DOWN
wss://relay.damus.io 2095 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 3060 27.3% 8988132 2937 19622
5 Deletion 1124 10.0% 498378 443 664
24747 Ephemeral 1020 9.1% 602037 590 671
22668 Ephemeral 547 4.9% 234663 429 429
30078 App data 439 3.9% 1714659 3906 37853
20001 Ephemeral (key xchg) 395 3.5% 266277 674 1719
22734 Ephemeral 265 2.4% 113155 427 427
4515 Unknown 246 2.2% 390676 1588 8760
1 Text note 205 1.8% 164127 801 2024
1059 Gift wrap seal 188 1.7% 301940 1606 5285
23334 Ephemeral 179 1.6% 77621 434 438
38501 App data (btc) 157 1.4% 116283 741 744
30382 Addressable 138 1.2% 66879 485 498
21050 Ephemeral 132 1.2% 128700 975 2558
10002 Relay list 127 1.1% 178988 1409 42385
22850 Ephemeral 124 1.1% 53320 430 430
31991 Addressable 122 1.1% 56242 461 461
0 Profile metadata 120 1.1% 92899 774 7180
37195 Addressable 102 0.9% 77276 758 832
24242 Ephemeral 93 0.8% 56549 608 3281
25050 Ephemeral 90 0.8% 39930 444 465
7 Reaction 79 0.7% 42454 537 738
23337 Ephemeral 73 0.7% 37477 513 519
24133 Ephemeral 71 0.6% 146874 2069 7334
445 Encrypted group msg 68 0.6% 386304 5681 38736
22601 Ephemeral 62 0.6% 26536 428 428
22613 Ephemeral 58 0.5% 40166 693 695
22749 Ephemeral 56 0.5% 24024 429 429
22699 Ephemeral 56 0.5% 23968 428 428
22676 Ephemeral 56 0.5% 23912 427 427
23003 Ephemeral 50 0.4% 21400 428 428
13194 NWC info 50 0.4% 25490 510 514
29333 Ephemeral 50 0.4% 24600 492 492
22795 Ephemeral 50 0.4% 22860 457 1839
22774 Ephemeral 50 0.4% 21401 428 429
20000 Ephemeral 50 0.4% 23047 461 536
22655 Ephemeral 49 0.4% 20972 428 428
31990 App handler info 46 0.4% 43151 938 1465
5300 Unknown 44 0.4% 27099 616 701
30383 Addressable 44 0.4% 23495 534 542
22456 Ephemeral 43 0.4% 21378 497 790
25555 Ephemeral 42 0.4% 175994 4190 16987
20004 Ephemeral 39 0.3% 20619 529 549
3927 Unknown 39 0.3% 20809 534 544
22484 Ephemeral 36 0.3% 32790 911 1165
11998 Unknown 33 0.3% 15297 464 465
7368 Unknown 32 0.3% 33232 1038 1041
20387 Ephemeral 31 0.3% 15934 514 514
22507 Ephemeral 28 0.2% 25861 924 1163
20787 Ephemeral 26 0.2% 34212 1316 2282
10100 Unknown 25 0.2% 48552 1942 3825
30091 Addressable 23 0.2% 14133 614 629
30088 Addressable 23 0.2% 42847 1863 1871
38502 Addressable 22 0.2% 8470 385 385
4 Encrypted DM 22 0.2% 25752 1171 6250
1985 Label 22 0.2% 10436 474 483
10050 DM relays 21 0.2% 9383 447 465
30315 User status 20 0.2% 12697 635 926
22816 Ephemeral 20 0.2% 8580 429 429
4004 Unknown 19 0.2% 22211 1169 1169
22522 Ephemeral 18 0.2% 7632 424 424
22914 Ephemeral 17 0.2% 15202 894 1819
3 Follow list 15 0.1% 210884 14059 45848
22591 Ephemeral 15 0.1% 6390 426 426
30089 Chunked data 15 0.1% 221528 14769 44372
6300 Unknown 15 0.1% 83624 5575 17374
30023 Long-form article 15 0.1% 83310 5554 8177
20333 Ephemeral 15 0.1% 11950 797 798
22832 Ephemeral 14 0.1% 6006 429 429
1077 Unknown 14 0.1% 8498 607 607
22651 Ephemeral 14 0.1% 5992 428 428
30515 Addressable 14 0.1% 55160 3940 3940
23333 Ephemeral 11 0.1% 6455 587 601
2333 Unknown 11 0.1% 7423 675 689
30311 Addressable 10 0.1% 10569 1057 1214
21617 Ephemeral 10 0.1% 4950 495 495
30443 Addressable 10 0.1% 11954 1195 1292
9735 Zap receipt 10 0.1% 20601 2060 2282
1337 Unknown 10 0.1% 5696 570 811
25055 Ephemeral 10 0.1% 14724 1472 1764
21060 Ephemeral 9 0.1% 5130 570 570
38225 Addressable 9 0.1% 9197 1022 1047
22949 Ephemeral 9 0.1% 3879 431 431
4244 Unknown 9 0.1% 5076 564 564
7375 Unknown 9 0.1% 72189 8021 14089
7376 Unknown 9 0.1% 4689 521 521
38385 Addressable 9 0.1% 12280 1364 1496
31234 Addressable 9 0.1% 12744 1416 1416
22712 Ephemeral 8 0.1% 3424 428 428
38503 Addressable 8 0.1% 4424 553 553
30113 Addressable 8 0.1% 4688 586 701
6 Repost 8 0.1% 11116 1390 2553
31510 Addressable 7 0.1% 6853 979 982
22785 Ephemeral 7 0.1% 2996 428 428
22680 Ephemeral 7 0.1% 6191 884 1160
22793 Ephemeral 7 0.1% 2996 428 428
22684 Ephemeral 6 0.1% 2574 429 429
22225 Ephemeral 6 0.1% 2661 444 444
1002 Unknown 6 0.1% 5458 910 971
22739 Ephemeral 6 0.1% 2568 428 428
22658 Ephemeral 6 0.1% 5377 896 1164
30080 Addressable 5 0.0% 4658 932 985
33334 Addressable 5 0.0% 4155 831 831
10011 Unknown 5 0.0% 2155 431 431
25195 Ephemeral 5 0.0% 3390 678 678
22508 Ephemeral 5 0.0% 2130 426 426
22789 Ephemeral 5 0.0% 2140 428 428
30380 Addressable 5 0.0% 2270 454 454
38384 Addressable 5 0.0% 3715 743 743
20384 Ephemeral 5 0.0% 3195 639 639
22688 Ephemeral 5 0.0% 2150 430 430
22649 Ephemeral 5 0.0% 2135 427 427
39101 Addressable 5 0.0% 2665 533 533
22611 Ephemeral 5 0.0% 2130 426 426
22526 Ephemeral 5 0.0% 2150 430 430
22514 Ephemeral 4 0.0% 1700 425 425
22775 Ephemeral 4 0.0% 1716 429 429
443 Unknown 4 0.0% 4336 1084 1084
25196 Ephemeral 4 0.0% 7152 1788 2214
30618 Addressable 4 0.0% 6570 1642 4371
23194 NWC request 4 0.0% 2106 526 576
1040 Unknown 4 0.0% 4192 1048 1048
38500 Addressable 4 0.0% 2848 712 713
22236 Ephemeral 4 0.0% 3109 777 801
1111 Comment 4 0.0% 5593 1398 1988
3434 Unknown 3 0.0% 2035 678 705
1050 Unknown 3 0.0% 1311 437 437
33051 Addressable 3 0.0% 1559 520 522
7000 Unknown 3 0.0% 2329 776 779
22309 Ephemeral 3 0.0% 1331 444 444
30617 Addressable 3 0.0% 3267 1089 1089
10030 Emoji list 3 0.0% 1215 405 405
22253 Ephemeral 3 0.0% 1331 444 444
22345 Ephemeral 3 0.0% 1331 444 444
4245 Unknown 3 0.0% 1332 444 444
22423 Ephemeral 3 0.0% 1331 444 444
4243 Unknown 3 0.0% 1692 564 564
10000 Mute list 3 0.0% 4678 1559 3850
22352 Ephemeral 3 0.0% 1330 443 444
38383 Addressable 3 0.0% 2159 720 743
22327 Ephemeral 3 0.0% 1330 443 444
30213 Addressable 3 0.0% 1880 627 637
20 Picture 2 0.0% 1272 636 827
10312 Unknown 2 0.0% 973 486 491
10007 Search relays 2 0.0% 1272 636 754
10086 Unknown 2 0.0% 1451 726 757
22891 Ephemeral 2 0.0% 2272 1136 1843
22495 Ephemeral 2 0.0% 854 427 427
4242 Unknown 2 0.0% 12868 6434 6443
30000 Follow sets 2 0.0% 1773 886 950
22777 Ephemeral 2 0.0% 856 428 428
22391 Ephemeral 2 0.0% 887 444 444
6472 Unknown 2 0.0% 1106 553 553
22239 Ephemeral 2 0.0% 887 444 444
22146 Ephemeral 2 0.0% 886 443 443
22420 Ephemeral 2 0.0% 887 444 444
10063 Unknown 2 0.0% 1012 506 627
22193 Ephemeral 2 0.0% 886 443 443
22275 Ephemeral 2 0.0% 886 443 443
31000 Addressable 2 0.0% 1204 602 616
22738 Ephemeral 2 0.0% 858 429 429
31340 Addressable 2 0.0% 854 427 430
22218 Ephemeral 2 0.0% 887 444 444
22285 Ephemeral 2 0.0% 887 444 444
7403 Unknown 2 0.0% 982 491 491
22302 Ephemeral 2 0.0% 886 443 443
22241 Ephemeral 2 0.0% 887 444 444
22288 Ephemeral 2 0.0% 887 444 444
22120 Ephemeral 2 0.0% 2170 1085 1118
22119 Ephemeral 2 0.0% 1691 846 846
15750 Unknown 2 0.0% 1184 592 620
10443 Unknown 2 0.0% 2578 1289 1289
22736 Ephemeral 1 0.0% 429 429 429
22653 Ephemeral 1 0.0% 1840 1840 1840
22842 Ephemeral 1 0.0% 1847 1847 1847
22190 Ephemeral 1 0.0% 443 443 443
37779 Addressable 1 0.0% 523 523 523
22312 Ephemeral 1 0.0% 443 443 443
1717 Unknown 1 0.0% 1776 1776 1776
22407 Ephemeral 1 0.0% 425 425 425
1340 Unknown 1 0.0% 435 435 435
10051 Unknown 1 0.0% 443 443 443
10012 Unknown 1 0.0% 522 522 522
22368 Ephemeral 1 0.0% 443 443 443
10003 Bookmarks 1 0.0% 1974 1974 1974
39015 Addressable 1 0.0% 696 696 696
1311 Unknown 1 0.0% 534 534 534
22820 Ephemeral 1 0.0% 430 430 430
22928 Ephemeral 1 0.0% 429 429 429
22713 Ephemeral 1 0.0% 429 429 429
22543 Ephemeral 1 0.0% 1843 1843 1843
22369 Ephemeral 1 0.0% 443 443 443
22314 Ephemeral 1 0.0% 443 443 443
10001 Pinned notes 1 0.0% 710 710 710
30815 Addressable 1 0.0% 37534 37534 37534
31339 Addressable 1 0.0% 1470 1470 1470
38422 Addressable 1 0.0% 20019 20019 20019
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
10089 Unknown 1 0.0% 1097 1097 1097
13196 Unknown 1 0.0% 656 656 656
39050 Addressable 1 0.0% 1843 1843 1843
10005 Unknown 1 0.0% 694 694 694
30554 Addressable 1 0.0% 609 609 609
22765 Ephemeral 1 0.0% 431 431 431
1078 Unknown 1 0.0% 13949 13949 13949
22740 Ephemeral 1 0.0% 3194 3194 3194
38111 Addressable 1 0.0% 747 747 747
4747 Unknown 1 0.0% 721 721 721
10019 Unknown 1 0.0% 536 536 536
22333 Ephemeral 1 0.0% 443 443 443
22318 Ephemeral 1 0.0% 443 443 443
23010 Ephemeral 1 0.0% 429 429 429
30079 Addressable 1 0.0% 875 875 875
22455 Ephemeral 1 0.0% 1840 1840 1840
22219 Ephemeral 1 0.0% 443 443 443
30787 Addressable 1 0.0% 1370 1370 1370
22624 Ephemeral 1 0.0% 3198 3198 3198
22750 Ephemeral 1 0.0% 553 553 553
22313 Ephemeral 1 0.0% 443 443 443
1080 Unknown 1 0.0% 1033 1033 1033
11316 Unknown 1 0.0% 725 725 725
11317 Unknown 1 0.0% 12051 12051 12051
12222 Unknown 1 0.0% 579 579 579
22823 Ephemeral 1 0.0% 429 429 429
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:32:17 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
[2026-08-02 09:32:24] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,207 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 2m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 4267
Total bytes: 6726611 (6.41 MB)
Avg event size: 1576 bytes
Max event: 42385 bytes (kind 10002)
Unique pubkeys: 1860
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 1670 1 0 0 0 0 OK
wss://nos.lol 1792 0 0 0 0 0 OK
wss://relay.damus.io 805 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 1199 28.1% 3547818 2959 5314
5 Deletion 466 10.9% 204587 439 1064
30078 App data 222 5.2% 948782 4274 40337
22668 Ephemeral 221 5.2% 94809 429 429
20001 Ephemeral (key xchg) 152 3.6% 106536 701 1755
4515 Unknown 152 3.6% 260732 1715 8759
22734 Ephemeral 108 2.5% 46116 427 427
1 Text note 105 2.5% 83026 791 3482
21050 Ephemeral 91 2.1% 89206 980 2558
24747 Ephemeral 85 2.0% 42775 503 671
22850 Ephemeral 77 1.8% 33110 430 430
22591 Ephemeral 66 1.5% 53607 812 1222
38501 App data (btc) 61 1.4% 45175 741 744
10002 Relay list 51 1.2% 63749 1250 42385
0 Profile metadata 47 1.1% 33672 716 1611
37195 Addressable 44 1.0% 32855 747 771
24242 Ephemeral 37 0.9% 21423 579 579
25050 Ephemeral 36 0.8% 15972 444 465
7 Reaction 36 0.8% 18965 527 646
22613 Ephemeral 35 0.8% 26154 747 1134
22484 Ephemeral 32 0.7% 29125 910 1163
22749 Ephemeral 29 0.7% 12441 429 429
24133 Ephemeral 25 0.6% 37446 1498 7334
22601 Ephemeral 25 0.6% 10700 428 428
22507 Ephemeral 24 0.6% 22582 941 1165
1059 Gift wrap seal 23 0.5% 41391 1800 2897
22699 Ephemeral 23 0.5% 9844 428 428
22676 Ephemeral 23 0.5% 9821 427 427
22774 Ephemeral 20 0.5% 8560 428 428
22795 Ephemeral 20 0.5% 8580 429 429
22655 Ephemeral 20 0.5% 8560 428 428
23003 Ephemeral 20 0.5% 8560 428 428
30382 Addressable 20 0.5% 9960 498 498
29333 Ephemeral 20 0.5% 9840 492 492
25555 Ephemeral 18 0.4% 62434 3469 17003
11998 Unknown 18 0.4% 8322 462 465
31990 App handler info 18 0.4% 16845 936 1465
4 Encrypted DM 18 0.4% 28064 1559 17018
13194 NWC info 18 0.4% 9252 514 514
22689 Ephemeral 17 0.4% 7259 427 427
5300 Unknown 17 0.4% 10495 617 701
445 Encrypted group msg 17 0.4% 81892 4817 19620
20000 Ephemeral 15 0.4% 7045 470 546
20387 Ephemeral 14 0.3% 7196 514 514
20004 Ephemeral 14 0.3% 7488 535 549
7368 Unknown 13 0.3% 13476 1037 1040
22456 Ephemeral 13 0.3% 6110 470 470
9735 Zap receipt 13 0.3% 26072 2006 2331
22551 Ephemeral 13 0.3% 11042 849 1161
22760 Ephemeral 13 0.3% 11014 847 1161
22658 Ephemeral 13 0.3% 11050 850 1164
10100 Unknown 11 0.3% 20684 1880 3825
30315 User status 11 0.3% 8899 809 1038
20787 Ephemeral 11 0.3% 15458 1405 2474
22236 Ephemeral 10 0.2% 7818 782 814
22832 Ephemeral 10 0.2% 4286 429 429
38502 Addressable 8 0.2% 3080 385 385
4004 Unknown 8 0.2% 9352 1169 1169
22785 Ephemeral 8 0.2% 3432 429 429
3 Follow list 8 0.2% 35850 4481 9845
1985 Label 8 0.2% 3792 474 474
22584 Ephemeral 8 0.2% 3416 427 427
22816 Ephemeral 8 0.2% 3432 429 429
22752 Ephemeral 8 0.2% 6212 776 3199
6 Repost 7 0.2% 10112 1445 1872
443 Unknown 7 0.2% 7780 1111 1116
22651 Ephemeral 6 0.1% 2568 428 428
1077 Unknown 6 0.1% 3642 607 607
26428 Ephemeral 6 0.1% 5254 876 876
22616 Ephemeral 6 0.1% 2574 429 429
22626 Ephemeral 6 0.1% 2568 428 428
22914 Ephemeral 6 0.1% 2580 430 430
22614 Ephemeral 6 0.1% 2568 428 428
22419 Ephemeral 5 0.1% 2218 444 444
22847 Ephemeral 5 0.1% 2155 431 431
30088 Addressable 5 0.1% 9317 1863 1871
10050 DM relays 5 0.1% 2250 450 450
30091 Addressable 5 0.1% 3085 617 629
30311 Addressable 5 0.1% 5153 1031 1197
23080 Ephemeral 5 0.1% 2155 431 431
38225 Addressable 4 0.1% 4077 1019 1036
4244 Unknown 4 0.1% 2256 564 564
26429 Ephemeral 4 0.1% 1964 491 491
3927 Unknown 4 0.1% 2132 533 533
22806 Ephemeral 4 0.1% 1712 428 428
30000 Follow sets 4 0.1% 9358 2340 2845
21617 Ephemeral 4 0.1% 1980 495 495
20333 Ephemeral 4 0.1% 3188 797 798
22548 Ephemeral 4 0.1% 1720 430 430
38503 Addressable 4 0.1% 2212 553 553
30515 Addressable 4 0.1% 18496 4624 4624
22740 Ephemeral 4 0.1% 1708 427 427
22226 Ephemeral 3 0.1% 1331 444 444
10011 Unknown 3 0.1% 1375 458 513
30089 Chunked data 3 0.1% 6944 2315 3412
30023 Long-form article 3 0.1% 19822 6607 7860
21060 Ephemeral 3 0.1% 1710 570 570
22526 Ephemeral 3 0.1% 1290 430 430
22511 Ephemeral 3 0.1% 2383 794 1163
22167 Ephemeral 3 0.1% 1331 444 444
30213 Addressable 3 0.1% 1866 622 632
22748 Ephemeral 3 0.1% 1284 428 428
31234 Addressable 3 0.1% 4248 1416 1416
22838 Ephemeral 2 0.0% 860 430 430
38500 Addressable 2 0.0% 1424 712 713
38384 Addressable 2 0.0% 1486 743 743
30443 Addressable 2 0.0% 2379 1190 1220
20384 Ephemeral 2 0.0% 1278 639 639
34237 Addressable 2 0.0% 836 418 418
22280 Ephemeral 2 0.0% 887 444 444
23194 NWC request 2 0.0% 1020 510 510
22320 Ephemeral 2 0.0% 887 444 444
30080 Addressable 2 0.0% 1971 986 986
22205 Ephemeral 2 0.0% 887 444 444
22770 Ephemeral 2 0.0% 862 431 431
22305 Ephemeral 2 0.0% 887 444 444
22356 Ephemeral 2 0.0% 886 443 443
22611 Ephemeral 2 0.0% 852 426 426
22217 Ephemeral 2 0.0% 887 444 444
22884 Ephemeral 2 0.0% 860 430 430
33334 Addressable 2 0.0% 1662 831 831
6300 Unknown 2 0.0% 8371 4186 5058
22169 Ephemeral 2 0.0% 887 444 444
22190 Ephemeral 2 0.0% 887 444 444
30079 Addressable 2 0.0% 1750 875 875
39101 Addressable 2 0.0% 1066 533 533
22345 Ephemeral 2 0.0% 887 444 444
22310 Ephemeral 2 0.0% 887 444 444
1111 Comment 2 0.0% 2557 1278 1564
22235 Ephemeral 2 0.0% 887 444 444
38385 Addressable 2 0.0% 2552 1276 1319
4747 Unknown 1 0.0% 721 721 721
22844 Ephemeral 1 0.0% 428 428 428
22807 Ephemeral 1 0.0% 3205 3205 3205
30066 Addressable 1 0.0% 675 675 675
4245 Unknown 1 0.0% 444 444 444
7376 Unknown 1 0.0% 521 521 521
22450 Ephemeral 1 0.0% 443 443 443
22261 Ephemeral 1 0.0% 443 443 443
4243 Unknown 1 0.0% 564 564 564
22242 Ephemeral 1 0.0% 443 443 443
10063 Unknown 1 0.0% 415 415 415
22306 Ephemeral 1 0.0% 443 443 443
22174 Ephemeral 1 0.0% 443 443 443
22831 Ephemeral 1 0.0% 3182 3182 3182
22227 Ephemeral 1 0.0% 443 443 443
7375 Unknown 1 0.0% 8629 8629 8629
22685 Ephemeral 1 0.0% 553 553 553
22299 Ephemeral 1 0.0% 443 443 443
22193 Ephemeral 1 0.0% 443 443 443
22975 Ephemeral 1 0.0% 3195 3195 3195
30380 Addressable 1 0.0% 454 454 454
20 Picture 1 0.0% 860 860 860
10030 Emoji list 1 0.0% 405 405 405
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
22220 Ephemeral 1 0.0% 443 443 443
39019 Addressable 1 0.0% 961 961 961
39015 Addressable 1 0.0% 696 696 696
30617 Addressable 1 0.0% 1089 1089 1089
30618 Addressable 1 0.0% 733 733 733
31991 Addressable 1 0.0% 461 461 461
1080 Unknown 1 0.0% 949 949 949
3434 Unknown 1 0.0% 705 705 705
33333 Addressable 1 0.0% 2346 2346 2346
22735 Ephemeral 1 0.0% 430 430 430
22415 Ephemeral 1 0.0% 443 443 443
21966 Ephemeral 1 0.0% 443 443 443
11316 Unknown 1 0.0% 725 725 725
11317 Unknown 1 0.0% 12051 12051 12051
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
22402 Ephemeral 1 0.0% 443 443 443
31510 Addressable 1 0.0% 979 979 979
1078 Unknown 1 0.0% 14200 14200 14200
38383 Addressable 1 0.0% 685 685 685
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:38:02 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 09:38:07] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 09:38:07] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 09:38:07] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 09:38:08] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,206 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 2m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 5012
Total bytes: 8091410 (7.72 MB)
Avg event size: 1614 bytes
Max event: 52672 bytes (kind 3)
Unique pubkeys: 2034
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 1599 1 0 0 0 0 OK
wss://nos.lol 2389 0 0 0 0 0 OK
wss://relay.damus.io 1024 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 1361 27.2% 4189338 3078 5314
5 Deletion 579 11.6% 254363 439 624
22668 Ephemeral 265 5.3% 113685 429 429
30078 App data 190 3.8% 702178 3696 14343
20001 Ephemeral (key xchg) 181 3.6% 108257 598 1489
22639 Ephemeral 176 3.5% 136058 773 1228
4515 Unknown 168 3.4% 319242 1900 14220
24133 Ephemeral 152 3.0% 464764 3058 14162
22734 Ephemeral 135 2.7% 57645 427 427
31991 Addressable 116 2.3% 53476 461 461
21050 Ephemeral 84 1.7% 81900 975 2558
1 Text note 80 1.6% 58412 730 1443
22795 Ephemeral 76 1.5% 104000 1368 1848
22850 Ephemeral 75 1.5% 33353 445 1169
38501 App data (btc) 69 1.4% 51105 741 744
22842 Ephemeral 55 1.1% 92475 1681 1849
10002 Relay list 48 1.0% 19784 412 537
0 Profile metadata 47 0.9% 34961 744 1356
37195 Addressable 38 0.8% 28214 742 771
7 Reaction 38 0.8% 21600 568 779
25050 Ephemeral 36 0.7% 15972 444 465
24242 Ephemeral 34 0.7% 19686 579 579
1059 Gift wrap seal 33 0.7% 58147 1762 3298
22484 Ephemeral 32 0.6% 29156 911 1164
22507 Ephemeral 28 0.6% 24308 868 1165
22613 Ephemeral 25 0.5% 17238 690 691
29333 Ephemeral 25 0.5% 12300 492 492
22601 Ephemeral 24 0.5% 10272 428 428
5300 Unknown 23 0.5% 14096 613 701
22751 Ephemeral 23 0.5% 9798 426 426
22699 Ephemeral 22 0.4% 9416 428 428
11998 Unknown 22 0.4% 10182 463 465
22676 Ephemeral 22 0.4% 9394 427 427
13194 NWC info 21 0.4% 10617 506 514
22774 Ephemeral 20 0.4% 8560 428 428
23003 Ephemeral 20 0.4% 8560 428 428
22655 Ephemeral 19 0.4% 8132 428 428
22543 Ephemeral 19 0.4% 34993 1842 1848
22455 Ephemeral 19 0.4% 34943 1839 1844
25555 Ephemeral 19 0.4% 94673 4983 17111
30443 Addressable 19 0.4% 22379 1178 1260
22777 Ephemeral 19 0.4% 16685 878 1651
22891 Ephemeral 18 0.4% 33189 1844 1848
31990 App handler info 17 0.3% 15825 931 1465
20000 Ephemeral 16 0.3% 7206 450 522
32123 Addressable 14 0.3% 10528 752 900
22456 Ephemeral 13 0.3% 6110 470 470
20004 Ephemeral 13 0.3% 6741 519 549
22591 Ephemeral 13 0.3% 6348 488 697
20787 Ephemeral 12 0.2% 16864 1405 2478
4 Encrypted DM 12 0.2% 7972 664 786
20387 Ephemeral 12 0.2% 6168 514 514
38502 Addressable 12 0.2% 4620 385 385
10100 Unknown 11 0.2% 20938 1903 3825
20050 Ephemeral 11 0.2% 9711 883 891
3 Follow list 10 0.2% 72137 7214 52672
9735 Zap receipt 10 0.2% 20833 2083 2143
445 Encrypted group msg 10 0.2% 17866 1787 4126
22626 Ephemeral 10 0.2% 4280 428 428
7368 Unknown 9 0.2% 9343 1038 1041
22736 Ephemeral 9 0.2% 3816 424 424
1985 Label 8 0.2% 3810 476 483
30091 Addressable 8 0.2% 4927 616 629
30315 User status 8 0.2% 4522 565 872
25055 Ephemeral 8 0.2% 10605 1326 1764
22791 Ephemeral 8 0.2% 3416 427 427
22816 Ephemeral 8 0.2% 3432 429 429
4004 Unknown 8 0.2% 9352 1169 1169
30088 Addressable 8 0.2% 14929 1866 1871
31234 Addressable 8 0.2% 14048 1756 1756
22813 Ephemeral 7 0.1% 2996 428 428
22957 Ephemeral 7 0.1% 3010 430 430
30089 Chunked data 7 0.1% 45972 6567 27988
32678 Addressable 7 0.1% 5704 815 900
22832 Ephemeral 6 0.1% 2574 429 429
22897 Ephemeral 6 0.1% 2586 431 431
22549 Ephemeral 6 0.1% 2574 429 429
22702 Ephemeral 6 0.1% 4753 792 2618
20333 Ephemeral 6 0.1% 4780 797 798
30023 Long-form article 6 0.1% 21101 3517 5056
22568 Ephemeral 5 0.1% 2135 427 427
38385 Addressable 5 0.1% 6849 1370 1496
4244 Unknown 5 0.1% 2820 564 564
22717 Ephemeral 5 0.1% 2135 427 427
21617 Ephemeral 4 0.1% 1980 495 495
38225 Addressable 4 0.1% 4061 1015 1039
6 Repost 4 0.1% 8823 2206 3647
22333 Ephemeral 4 0.1% 1773 443 444
22962 Ephemeral 3 0.1% 1290 430 430
33334 Addressable 3 0.1% 2229 743 831
1002 Unknown 3 0.1% 2793 931 979
22244 Ephemeral 3 0.1% 1330 443 444
22907 Ephemeral 3 0.1% 1287 429 429
10011 Unknown 3 0.1% 1229 410 431
22258 Ephemeral 3 0.1% 1330 443 444
22245 Ephemeral 3 0.1% 1331 444 444
30618 Addressable 3 0.1% 1755 585 733
22296 Ephemeral 3 0.1% 1331 444 444
38503 Addressable 3 0.1% 1659 553 553
22338 Ephemeral 3 0.1% 1330 443 444
22195 Ephemeral 3 0.1% 1331 444 444
22342 Ephemeral 3 0.1% 1331 444 444
10050 DM relays 3 0.1% 1350 450 450
30311 Addressable 3 0.1% 3414 1138 1197
32680 Addressable 2 0.0% 1376 688 688
30380 Addressable 2 0.0% 908 454 454
22611 Ephemeral 2 0.0% 852 426 426
4243 Unknown 2 0.0% 1128 564 564
22526 Ephemeral 2 0.0% 860 430 430
22320 Ephemeral 2 0.0% 887 444 444
4245 Unknown 2 0.0% 888 444 444
1984 Report 2 0.0% 873 436 446
1111 Comment 2 0.0% 3115 1558 1558
22311 Ephemeral 2 0.0% 886 443 443
22662 Ephemeral 2 0.0% 858 429 429
22089 Ephemeral 2 0.0% 886 443 443
22221 Ephemeral 2 0.0% 887 444 444
22157 Ephemeral 2 0.0% 887 444 444
38384 Addressable 2 0.0% 1486 743 743
20384 Ephemeral 2 0.0% 1278 639 639
30080 Addressable 2 0.0% 1679 840 985
39101 Addressable 2 0.0% 1066 533 533
6300 Unknown 2 0.0% 11904 5952 6898
10003 Bookmarks 2 0.0% 14466 7233 7233
30515 Addressable 2 0.0% 9248 4624 4624
22829 Ephemeral 2 0.0% 6387 3194 3194
22106 Ephemeral 2 0.0% 887 444 444
31927 Addressable 2 0.0% 3000 1500 1500
31510 Addressable 2 0.0% 1959 980 980
3927 Unknown 2 0.0% 1066 533 533
1040 Unknown 2 0.0% 2096 1048 1048
31000 Addressable 2 0.0% 1204 602 616
22201 Ephemeral 2 0.0% 886 443 443
22269 Ephemeral 2 0.0% 887 444 444
22818 Ephemeral 2 0.0% 852 426 426
22364 Ephemeral 2 0.0% 887 444 444
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
22283 Ephemeral 1 0.0% 443 443 443
10030 Emoji list 1 0.0% 405 405 405
22706 Ephemeral 1 0.0% 3192 3192 3192
22236 Ephemeral 1 0.0% 737 737 737
31339 Addressable 1 0.0% 1470 1470 1470
22651 Ephemeral 1 0.0% 428 428 428
38500 Addressable 1 0.0% 713 713 713
10000 Mute list 1 0.0% 1311 1311 1311
31926 Addressable 1 0.0% 1038 1038 1038
22757 Ephemeral 1 0.0% 428 428 428
39015 Addressable 1 0.0% 696 696 696
30617 Addressable 1 0.0% 1089 1089 1089
22234 Ephemeral 1 0.0% 443 443 443
30815 Addressable 1 0.0% 37534 37534 37534
1078 Unknown 1 0.0% 13939 13939 13939
39050 Addressable 1 0.0% 1843 1843 1843
22281 Ephemeral 1 0.0% 443 443 443
30166 Addressable 1 0.0% 808 808 808
22772 Ephemeral 1 0.0% 429 429 429
22356 Ephemeral 1 0.0% 443 443 443
30554 Addressable 1 0.0% 609 609 609
38422 Addressable 1 0.0% 20025 20025 20025
38000 Addressable 1 0.0% 562 562 562
22069 Ephemeral 1 0.0% 443 443 443
1050 Unknown 1 0.0% 437 437 437
33051 Addressable 1 0.0% 522 522 522
39036 Addressable 1 0.0% 1138 1138 1138
23194 NWC request 1 0.0% 510 510 510
30081 Addressable 1 0.0% 1370 1370 1370
22299 Ephemeral 1 0.0% 443 443 443
22556 Ephemeral 1 0.0% 427 427 427
22302 Ephemeral 1 0.0% 443 443 443
39019 Addressable 1 0.0% 961 961 961
30000 Follow sets 1 0.0% 823 823 823
38383 Addressable 1 0.0% 657 657 657
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:48:55 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 09:49:03] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,51 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 27
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 0 0 0 0 0 0 DOWN
wss://spatia-arcana.com/inbox 0 0 0 0 0 0 DOWN
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 0 0 0 0 0 0 DOWN
wss://wot.utxo.one 0 0 0 0 0 0 DOWN
wss://premium.primal.net 0 0 0 0 0 0 DOWN
wss://nostr.mom 0 0 0 0 0 0 DOWN
wss://relay.mostr.pub 0 0 0 0 0 0 DOWN
wss://offchain.pub 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://nostr-pub.wellorder.net 0 0 0 0 0 0 DOWN
wss://devrelay.azzamo.net 0 0 0 0 0 0 DOWN
wss://relay.noderunners.network 0 0 0 0 0 0 DOWN
wss://nostr-pub.semisol.dev 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostrcheck.me 0 0 0 0 0 0 DOWN
wss://relay.nostr.info 0 0 0 0 0 0 DOWN
wss://relay.momostr.pink 0 0 0 0 0 0 DOWN
wss://bitsat.molonlabe.holdings 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:01:51 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
@@ -0,0 +1,39 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 0 0 0 0 0 0 DOWN
wss://pyramid.fiatjaf.com/inbox 0 0 0 0 0 0 DOWN
wss://relay.mostr.pub 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://relay.nostr.info 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:10:55 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
@@ -0,0 +1,35 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 11
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.bitcoiner.social 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://pyramid.fiatjaf.com/inbox 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:17:20 2026
# Config: cache_all_test_config.jsonc
# Duration: 30 seconds
@@ -0,0 +1,197 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 1m 54s
=== Global Summary ===
Total events: 4719
Total bytes: 6608416 (6.30 MB)
Avg event size: 1400 bytes
Max event: 44372 bytes (kind 30089)
Unique pubkeys: 1333
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 804 0 0 0 0 3 OK
wss://nostr-pub.wellorder.net 83 0 0 0 0 0 OK
wss://nostr.mom 175 0 0 0 0 0 OK
wss://nostr.oxtr.dev 108 0 0 0 0 0 OK
wss://offchain.pub 627 0 0 0 0 0 OK
wss://premium.primal.net 1 0 0 0 0 0 OK
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 721 0 0 0 0 0 OK
wss://relay.divine.video 3 0 0 0 0 0 OK
wss://relay.momostr.pink 86 0 0 0 0 0 OK
wss://relay.mostr.pub 836 0 0 0 0 0 OK
wss://relay.nostrplebs.com 4 0 0 0 0 0 OK
wss://relay.primal.net 1239 0 0 0 0 0 OK
wss://theforest.nostr1.com 32 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 675 14.3% 2192938 3249 5314
30382 Addressable 563 11.9% 255591 454 498
5 Deletion 341 7.2% 153667 451 1404
22639 Ephemeral 231 4.9% 178011 771 1230
22551 Ephemeral 182 3.9% 78260 430 430
20001 Ephemeral 143 3.0% 70427 492 1206
30078 App data 141 3.0% 1361122 9653 44169
0 Profile metadata 135 2.9% 119793 887 3483
22734 Ephemeral 122 2.6% 52094 427 427
24747 Ephemeral 101 2.1% 59352 588 594
1 Text note 90 1.9% 92140 1024 4709
22668 Ephemeral 88 1.9% 37752 429 429
22850 Ephemeral 83 1.8% 35690 430 430
22870 Ephemeral 76 1.6% 77061 1014 2202
22951 Ephemeral 75 1.6% 77282 1030 2204
10002 Relay list 75 1.6% 202196 2696 42385
22912 Ephemeral 74 1.6% 31672 428 428
38501 Addressable 65 1.4% 48152 741 744
24133 Ephemeral 63 1.3% 72262 1147 2898
6 Repost 50 1.1% 40985 820 3346
37195 Addressable 48 1.0% 36225 755 771
21050 Ephemeral 48 1.0% 46800 975 2558
4515 Unknown 46 1.0% 81638 1775 6028
25555 Ephemeral 45 1.0% 209551 4657 16603
7 Reaction 40 0.8% 22486 562 805
22917 Ephemeral 39 0.8% 35435 909 1166
22699 Ephemeral 39 0.8% 26723 685 1166
22690 Ephemeral 39 0.8% 16770 430 430
22918 Ephemeral 38 0.8% 36470 960 1507
22832 Ephemeral 37 0.8% 16128 436 446
25050 Ephemeral 35 0.7% 15507 443 465
30383 Addressable 35 0.7% 18766 536 541
22713 Ephemeral 34 0.7% 14570 429 430
22698 Ephemeral 34 0.7% 31371 923 1674
22601 Ephemeral 28 0.6% 11984 428 428
22655 Ephemeral 28 0.6% 11984 428 428
1059 Gift wrap seal 23 0.5% 46912 2040 5285
22751 Ephemeral 21 0.4% 8946 426 426
22676 Ephemeral 21 0.4% 8967 427 427
29333 Ephemeral 20 0.4% 9840 492 492
5300 Unknown 20 0.4% 11959 598 630
22743 Ephemeral 19 0.4% 8132 428 428
22603 Ephemeral 19 0.4% 8094 426 426
22693 Ephemeral 19 0.4% 8113 427 427
22774 Ephemeral 19 0.4% 8132 428 428
22808 Ephemeral 19 0.4% 8151 429 429
22575 Ephemeral 19 0.4% 8075 425 425
23003 Ephemeral 19 0.4% 8132 428 428
22949 Ephemeral 19 0.4% 8151 429 429
23334 Ephemeral 19 0.4% 8189 431 431
24242 Ephemeral 17 0.4% 9843 579 579
22816 Ephemeral 16 0.3% 6864 429 429
20787 Ephemeral 16 0.3% 25100 1569 2478
13194 NWC info 15 0.3% 7574 505 514
38502 Addressable 14 0.3% 5390 385 385
22236 Ephemeral 14 0.3% 11214 801 814
22613 Ephemeral 13 0.3% 8977 691 694
30088 Addressable 12 0.3% 22340 1862 1871
22484 Ephemeral 12 0.3% 10948 912 1165
30091 Addressable 12 0.3% 7385 615 629
20004 Ephemeral 11 0.2% 5643 513 549
31990 App handler info 11 0.2% 8711 792 1465
445 Encrypted group msg 10 0.2% 17972 1797 4126
22795 Ephemeral 9 0.2% 3861 429 429
22507 Ephemeral 9 0.2% 8480 942 1165
22922 Ephemeral 9 0.2% 3870 430 430
24421 Ephemeral 9 0.2% 4644 516 516
22830 Ephemeral 9 0.2% 7317 813 1168
20000 Ephemeral 9 0.2% 3854 428 484
22955 Ephemeral 9 0.2% 3852 428 428
22793 Ephemeral 9 0.2% 3843 427 427
30315 User status 8 0.2% 4604 576 926
20387 Ephemeral 8 0.2% 4112 514 514
7368 Unknown 8 0.2% 8309 1039 1041
4004 Unknown 8 0.2% 9352 1169 1169
443 Unknown 8 0.2% 8928 1116 1116
11998 Unknown 7 0.1% 3255 465 465
10100 Unknown 7 0.1% 11851 1693 3825
22456 Ephemeral 7 0.1% 3290 470 470
30089 Chunked data 7 0.1% 157322 22475 44372
22737 Ephemeral 7 0.1% 4669 667 707
23337 Ephemeral 7 0.1% 3557 508 509
30515 Addressable 7 0.1% 18032 2576 2576
30311 Addressable 6 0.1% 5818 970 1197
1985 Unknown 6 0.1% 2860 477 482
22859 Ephemeral 5 0.1% 8606 1721 2195
4 Encrypted DM 5 0.1% 3222 644 722
10011 Unknown 5 0.1% 2027 405 431
22764 Ephemeral 4 0.1% 1712 428 428
22777 Ephemeral 4 0.1% 2790 698 698
4244 Unknown 4 0.1% 2256 564 564
30023 Long-form article 4 0.1% 22789 5697 6817
9735 Zap receipt 4 0.1% 7033 1758 1805
20333 Ephemeral 4 0.1% 3188 797 798
3 Follow list 3 0.1% 26232 8744 14166
38503 Addressable 3 0.1% 1655 552 552
10050 DM relays 3 0.1% 1411 470 511
21617 Ephemeral 3 0.1% 1485 495 495
1984 Report 3 0.1% 1986 662 662
3927 Unknown 3 0.1% 1599 533 533
3434 Unknown 3 0.1% 1907 636 665
22514 Ephemeral 2 0.0% 856 428 428
33334 Addressable 2 0.0% 1662 831 831
38500 Addressable 2 0.0% 1424 712 713
22526 Ephemeral 2 0.0% 860 430 430
30380 Addressable 2 0.0% 908 454 454
23194 NWC request 2 0.0% 1020 510 510
23042 Ephemeral 2 0.0% 858 429 429
38385 Addressable 2 0.0% 2692 1346 1478
30443 Addressable 2 0.0% 2369 1184 1219
22747 Ephemeral 2 0.0% 2064 1032 1288
4245 Unknown 2 0.0% 888 444 444
1111 Comment 2 0.0% 1936 968 981
38383 Addressable 2 0.0% 1439 720 720
4243 Unknown 2 0.0% 1128 564 564
38225 Addressable 1 0.0% 978 978 978
30618 Addressable 1 0.0% 733 733 733
31510 Addressable 1 0.0% 981 981 981
30617 Addressable 1 0.0% 1089 1089 1089
1077 Unknown 1 0.0% 607 607 607
20201 Ephemeral 1 0.0% 491 491 491
20200 Ephemeral 1 0.0% 491 491 491
1078 Unknown 1 0.0% 14173 14173 14173
6300 Unknown 1 0.0% 3698 3698 3698
11316 Unknown 1 0.0% 791 791 791
11317 Unknown 1 0.0% 2873 2873 2873
17 Unknown 1 0.0% 610 610 610
23059 Ephemeral 1 0.0% 429 429 429
22806 Ephemeral 1 0.0% 1520 1520 1520
22120 Ephemeral 1 0.0% 1127 1127 1127
13196 Unknown 1 0.0% 656 656 656
24913 Ephemeral 1 0.0% 431 431 431
12222 Unknown 1 0.0% 579 579 579
1040 Unknown 1 0.0% 1048 1048 1048
22119 Ephemeral 1 0.0% 845 845 845
10312 Unknown 1 0.0% 472 472 472
3079 Unknown 1 0.0% 909 909 909
30000 Addressable 1 0.0% 594 594 594
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:25:47 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 10:26:23] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 10:26:23] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.damus.io STATUS: ?
[2026-08-02 10:26:23] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 10:26:59] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:26:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:27:59] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:27:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:28:10] [RELAY] wss://nos.lol CONNECTED
@@ -0,0 +1,257 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 5m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 14714
Total bytes: 20163747 (19.23 MB)
Avg event size: 1370 bytes
Max event: 51791 bytes (kind 30078)
Unique pubkeys: 3435
Relays contacted: 24
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 2820 0 0 0 0 2 OK
wss://nos.lol 2964 0 0 0 0 3 OK
wss://relay.damus.io 1999 0 0 0 0 0 OK
wss://relay.ditto.pub 2049 0 0 0 0 0 OK
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 599 0 0 0 0 1 OK
wss://spatia-arcana.com/inbox 0 0 0 0 0 0 DOWN
wss://nostrelites.org 0 0 0 0 0 0 DOWN
wss://offchain.pub 1431 0 0 0 0 0 OK
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://premium.primal.net 7 0 0 0 0 0 OK
wss://nostr.mom 412 0 0 0 0 0 OK
wss://relay.mostr.pub 2134 0 0 0 0 1 OK
wss://relay.divine.video 14 0 0 0 0 0 OK
wss://relay.nostrplebs.com 4 0 0 0 0 0 OK
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 67 0 0 0 0 0 OK
wss://nostr-pub.wellorder.net 214 0 0 0 0 0 OK
wss://devrelay.azzamo.net 0 0 0 0 0 0 DOWN
wss://relay.noderunners.network 0 0 0 0 0 0 OK
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostrcheck.me 0 0 0 0 0 0 DOWN
wss://basspistol.org 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 2477 16.8% 8220486 3319 19622
30382 Addressable 1558 10.6% 708564 455 498
5 Deletion 1219 8.3% 542263 445 1404
22639 Ephemeral 607 4.1% 467729 771 1230
20001 Ephemeral (key xchg) 519 3.5% 251716 485 1431
22551 Ephemeral 479 3.3% 205970 430 430
30078 App data 432 2.9% 3024405 7001 51791
24747 Ephemeral 317 2.2% 188378 594 650
22734 Ephemeral 315 2.1% 134505 427 427
22668 Ephemeral 307 2.1% 131703 429 429
1 Text note 307 2.1% 270378 881 3665
0 Profile metadata 300 2.0% 260832 869 9187
22850 Ephemeral 242 1.6% 104060 430 430
38501 App data (btc) 241 1.6% 178603 741 744
22912 Ephemeral 238 1.6% 101864 428 428
4515 Unknown 225 1.5% 376537 1673 6028
10002 Relay list 211 1.4% 309808 1468 42385
24133 Ephemeral 193 1.3% 237962 1233 14162
22951 Ephemeral 179 1.2% 170939 955 2204
22870 Ephemeral 172 1.2% 165571 963 2202
25555 Ephemeral 157 1.1% 776467 4946 16603
37195 Addressable 142 1.0% 107593 758 869
21050 Ephemeral 141 1.0% 136906 971 2558
6 Repost 139 0.9% 113883 819 4053
7 Reaction 116 0.8% 65547 565 786
22832 Ephemeral 112 0.8% 48745 435 446
22690 Ephemeral 109 0.7% 46870 430 430
30383 Addressable 100 0.7% 53571 536 543
22713 Ephemeral 98 0.7% 41994 429 430
22601 Ephemeral 93 0.6% 39804 428 428
25050 Ephemeral 87 0.6% 38343 441 465
1059 Gift wrap seal 84 0.6% 158090 1882 5285
22699 Ephemeral 83 0.6% 55625 670 1872
22655 Ephemeral 80 0.5% 34240 428 428
22917 Ephemeral 75 0.5% 67279 897 1166
22676 Ephemeral 72 0.5% 30744 427 427
29333 Ephemeral 70 0.5% 34440 492 492
22918 Ephemeral 67 0.5% 65746 981 1876
20787 Ephemeral 62 0.4% 84460 1362 2478
22456 Ephemeral 61 0.4% 29310 480 790
22808 Ephemeral 59 0.4% 25296 429 429
24242 Ephemeral 57 0.4% 33003 579 579
22816 Ephemeral 56 0.4% 24024 429 429
22603 Ephemeral 55 0.4% 23430 426 426
22743 Ephemeral 55 0.4% 23540 428 428
22693 Ephemeral 54 0.4% 23058 427 427
22575 Ephemeral 54 0.4% 22950 425 425
23334 Ephemeral 53 0.4% 22843 431 431
13194 NWC info 52 0.4% 26592 511 514
22751 Ephemeral 51 0.3% 21726 426 426
22949 Ephemeral 50 0.3% 21450 429 429
11998 Unknown 50 0.3% 23202 464 465
24421 Ephemeral 48 0.3% 24795 517 525
22236 Ephemeral 47 0.3% 37103 789 814
22484 Ephemeral 47 0.3% 42919 913 1165
3927 Unknown 45 0.3% 23985 533 533
23003 Ephemeral 45 0.3% 19260 428 428
22774 Ephemeral 45 0.3% 19260 428 428
31990 App handler info 44 0.3% 40657 924 1465
5300 Unknown 43 0.3% 25958 604 701
20387 Ephemeral 40 0.3% 20560 514 514
20000 Ephemeral 38 0.3% 15672 412 487
38502 Addressable 38 0.3% 14630 385 385
22507 Ephemeral 38 0.3% 34749 914 1167
445 Encrypted group msg 37 0.3% 62402 1687 4130
22698 Ephemeral 34 0.2% 31371 923 1674
4 Encrypted DM 33 0.2% 44743 1356 17018
30315 User status 31 0.2% 20374 657 1166
1985 Label 31 0.2% 28905 932 4831
22859 Ephemeral 31 0.2% 30700 990 2195
22613 Ephemeral 31 0.2% 21415 691 694
20004 Ephemeral 29 0.2% 15525 535 549
7368 Unknown 27 0.2% 28042 1039 1041
30091 Addressable 27 0.2% 16620 616 629
30088 Addressable 27 0.2% 50292 1863 1871
22598 Ephemeral 27 0.2% 23198 859 1162
22955 Ephemeral 25 0.2% 10700 428 428
4004 Unknown 25 0.2% 29225 1169 1169
22793 Ephemeral 25 0.2% 10675 427 427
22922 Ephemeral 25 0.2% 10750 430 430
22795 Ephemeral 25 0.2% 10725 429 429
10100 Unknown 24 0.2% 39530 1647 3825
22764 Ephemeral 22 0.1% 9416 428 428
30089 Chunked data 18 0.1% 260436 14469 44372
30311 Addressable 18 0.1% 18058 1003 1197
20333 Ephemeral 18 0.1% 14340 797 798
443 Unknown 18 0.1% 20088 1116 1116
38383 Addressable 18 0.1% 13557 753 972
22777 Ephemeral 16 0.1% 12435 777 1159
30023 Long-form article 16 0.1% 92998 5812 8050
4244 Unknown 15 0.1% 8460 564 564
30515 Addressable 14 0.1% 36064 2576 2576
22967 Ephemeral 13 0.1% 5603 431 431
10011 Unknown 13 0.1% 5283 406 431
3 Follow list 12 0.1% 97388 8116 24970
38503 Addressable 12 0.1% 6624 552 553
1077 Unknown 12 0.1% 7284 607 607
22550 Ephemeral 12 0.1% 5112 426 426
9735 Zap receipt 12 0.1% 21551 1796 1963
22591 Ephemeral 11 0.1% 4686 426 426
22830 Ephemeral 11 0.1% 8707 792 1168
23337 Ephemeral 11 0.1% 5589 508 509
38385 Addressable 9 0.1% 12290 1366 1478
10050 DM relays 9 0.1% 4160 462 511
39101 Addressable 8 0.1% 4264 533 533
31510 Addressable 8 0.1% 7834 979 982
1111 Comment 8 0.1% 9909 1239 1706
1002 Unknown 7 0.0% 6647 950 1009
22737 Ephemeral 7 0.0% 4669 667 707
33334 Addressable 6 0.0% 4986 831 831
38384 Addressable 6 0.0% 4458 743 743
20384 Ephemeral 6 0.0% 3834 639 639
22526 Ephemeral 6 0.0% 2580 430 430
30443 Addressable 6 0.0% 7288 1215 1260
3434 Unknown 6 0.0% 3902 650 665
21617 Ephemeral 6 0.0% 2970 495 495
38500 Addressable 6 0.0% 4272 712 713
30380 Addressable 6 0.0% 2724 454 454
22514 Ephemeral 5 0.0% 2140 428 428
4245 Unknown 5 0.0% 2220 444 444
4243 Unknown 5 0.0% 2820 564 564
6300 Unknown 5 0.0% 19347 3869 4596
30000 Follow sets 5 0.0% 3005 601 823
22611 Ephemeral 5 0.0% 2130 426 426
38225 Addressable 5 0.0% 4929 986 993
23042 Ephemeral 5 0.0% 2145 429 429
14 Unknown 4 0.0% 5446 1362 1550
23059 Ephemeral 4 0.0% 2853 713 1162
3079 Unknown 4 0.0% 3636 909 909
1984 Report 4 0.0% 2648 662 662
23194 NWC request 4 0.0% 2040 510 510
21060 Ephemeral 4 0.0% 2280 570 570
30080 Addressable 4 0.0% 3763 941 986
31234 Addressable 4 0.0% 4512 1128 1856
10003 Bookmarks 3 0.0% 10518 3506 3506
1632 Unknown 3 0.0% 2187 729 729
22120 Ephemeral 3 0.0% 3229 1076 1127
1000 Unknown 3 0.0% 5178 1726 1734
22119 Ephemeral 3 0.0% 2537 846 846
11317 Unknown 2 0.0% 5746 2873 2873
11316 Unknown 2 0.0% 1582 791 791
30617 Addressable 2 0.0% 2178 1089 1089
30618 Addressable 2 0.0% 1466 733 733
17 External reaction 2 0.0% 1220 610 610
31991 Addressable 2 0.0% 922 461 461
24913 Ephemeral 2 0.0% 862 431 431
31985 Addressable 2 0.0% 1267 634 667
1060 Gift wrap (alt) 2 0.0% 2268 1134 1134
22806 Ephemeral 2 0.0% 3040 1520 1520
31339 Addressable 2 0.0% 2940 1470 1470
30090 Addressable 2 0.0% 3030 1515 1515
10000 Mute list 2 0.0% 1963 982 1034
30087 Addressable 2 0.0% 14650 7325 7325
10312 Unknown 2 0.0% 963 482 491
30815 Addressable 2 0.0% 75068 37534 37534
20200 Ephemeral 2 0.0% 982 491 491
20201 Ephemeral 2 0.0% 982 491 491
22747 Ephemeral 2 0.0% 2064 1032 1288
30787 Addressable 1 0.0% 1370 1370 1370
1078 Unknown 1 0.0% 14173 14173 14173
13196 Unknown 1 0.0% 656 656 656
7403 Unknown 1 0.0% 491 491 491
15129 Unknown 1 0.0% 455 455 455
12222 Unknown 1 0.0% 579 579 579
10030 Emoji list 1 0.0% 405 405 405
38111 Addressable 1 0.0% 748 748 748
15750 Unknown 1 0.0% 616 616 616
30421 Addressable 1 0.0% 995 995 995
33333 Addressable 1 0.0% 2346 2346 2346
30079 Addressable 1 0.0% 875 875 875
39019 Addressable 1 0.0% 961 961 961
39015 Addressable 1 0.0% 696 696 696
5555 Unknown 1 0.0% 732 732 732
34235 Addressable 1 0.0% 1071 1071 1071
1040 Unknown 1 0.0% 1048 1048 1048
31000 Addressable 1 0.0% 616 616 616
10063 Unknown 1 0.0% 627 627 627
30554 Addressable 1 0.0% 609 609 609
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
30213 Addressable 1 0.0% 632 632 632
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:18:44 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
[2026-08-02 10:25:43] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://offchain.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr.mom CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.divine.video CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.nostrplebs.com CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://theforest.nostr1.com CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr-pub.wellorder.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.noderunners.network CONNECTED
[2026-08-02 10:26:30] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:26:30] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:27:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:27:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:28:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:55] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 10:28:55] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:29:49] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:29:50] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 10:29:50] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:29:51] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 10:30:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:30:00] [RELAY] wss://nos.lol CONNECTED
@@ -0,0 +1,167 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 32s
=== Global Summary ===
Total events: 1485
Total bytes: 1812166 (1.73 MB)
Avg event size: 1220 bytes
Max event: 44546 bytes (kind 31234)
Unique pubkeys: 511
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 247 0 0 0 0 0 OK
wss://nostr-pub.wellorder.net 37 0 0 0 0 0 OK
wss://nostr.mom 38 0 0 0 0 0 OK
wss://nostr.oxtr.dev 36 0 0 0 0 0 OK
wss://offchain.pub 113 0 0 0 0 0 OK
wss://premium.primal.net 1 0 0 0 0 0 OK
wss://relay.damus.io 194 0 0 0 0 0 OK
wss://relay.ditto.pub 277 0 0 0 0 1 OK
wss://relay.divine.video 2 0 0 0 0 0 OK
wss://relay.momostr.pink 27 0 0 0 0 0 OK
wss://relay.mostr.pub 148 0 0 0 0 0 OK
wss://relay.nostrplebs.com 0 0 0 0 0 0 OK
wss://relay.primal.net 360 0 0 0 0 0 OK
wss://theforest.nostr1.com 5 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 184 12.4% 623532 3389 5314
22591 Ephemeral 139 9.4% 107560 774 1223
30382 Addressable 119 8.0% 53663 451 458
30383 Addressable 119 8.0% 53605 450 536
5 Deletion 91 6.1% 39950 439 624
30078 App data 54 3.6% 179695 3328 14343
20001 Ephemeral 49 3.3% 35635 727 1871
0 Profile metadata 48 3.2% 39906 831 1468
22668 Ephemeral 48 3.2% 20592 429 429
1 Text note 45 3.0% 38547 857 2413
22734 Ephemeral 26 1.8% 11102 427 427
4515 Unknown 26 1.8% 38218 1470 2956
10002 Relay list 25 1.7% 11707 468 565
22850 Ephemeral 24 1.6% 10320 430 430
7 Reaction 24 1.6% 13528 564 780
22551 Ephemeral 24 1.6% 10320 430 430
31991 Addressable 20 1.3% 9220 461 461
38501 Addressable 18 1.2% 13341 741 744
22663 Ephemeral 17 1.1% 7276 428 428
25555 Ephemeral 15 1.0% 90493 6033 16887
22912 Ephemeral 13 0.9% 5564 428 428
1059 Gift wrap seal 13 0.9% 23775 1829 1883
22832 Ephemeral 12 0.8% 5233 436 446
22690 Ephemeral 11 0.7% 4730 430 430
22545 Ephemeral 11 0.7% 4697 427 427
25050 Ephemeral 10 0.7% 4394 439 465
37195 Addressable 10 0.7% 7494 749 771
22601 Ephemeral 10 0.7% 4280 428 428
22795 Ephemeral 10 0.7% 11639 1164 1487
22891 Ephemeral 9 0.6% 13331 1481 1490
22655 Ephemeral 9 0.6% 3852 428 428
22911 Ephemeral 9 0.6% 13328 1481 1487
24133 Ephemeral 9 0.6% 8250 917 1534
13194 NWC info 8 0.5% 4112 514 514
22647 Ephemeral 8 0.5% 3416 427 427
22695 Ephemeral 7 0.5% 10339 1477 1481
22653 Ephemeral 7 0.5% 10368 1481 1487
22455 Ephemeral 7 0.5% 10339 1477 1485
22543 Ephemeral 7 0.5% 10354 1479 1487
30315 User status 7 0.5% 2714 388 398
22676 Ephemeral 6 0.4% 2562 427 427
22575 Ephemeral 6 0.4% 2550 425 425
22689 Ephemeral 6 0.4% 8851 1475 1484
22913 Ephemeral 6 0.4% 2586 431 431
22774 Ephemeral 6 0.4% 2568 428 428
24242 Ephemeral 6 0.4% 3474 579 579
22808 Ephemeral 6 0.4% 2574 429 429
22842 Ephemeral 6 0.4% 8891 1482 1488
22603 Ephemeral 5 0.3% 2130 426 426
6 Repost 5 0.3% 10814 2163 7298
31990 App handler info 5 0.3% 4305 861 1321
22384 Ephemeral 5 0.3% 2130 426 426
22699 Ephemeral 5 0.3% 2140 428 428
22613 Ephemeral 5 0.3% 3443 689 691
38502 Addressable 5 0.3% 1925 385 385
23003 Ephemeral 5 0.3% 2140 428 428
22743 Ephemeral 5 0.3% 2140 428 428
29333 Ephemeral 5 0.3% 2460 492 492
20004 Ephemeral 4 0.3% 1800 450 549
4 Encrypted DM 4 0.3% 3072 768 826
20387 Ephemeral 4 0.3% 2056 514 514
22816 Ephemeral 4 0.3% 1716 429 429
20000 Ephemeral 4 0.3% 1872 468 628
22934 Ephemeral 3 0.2% 5535 1845 1846
1985 Unknown 2 0.1% 948 474 474
31234 Addressable 2 0.1% 89092 44546 44546
10777 Unknown 2 0.1% 976 488 488
4004 Unknown 2 0.1% 2338 1169 1169
9735 Zap receipt 2 0.1% 3926 1963 2135
1000 Unknown 2 0.1% 3415 1708 1712
25195 Ephemeral 2 0.1% 1356 678 678
30311 Addressable 2 0.1% 2210 1105 1198
25196 Ephemeral 2 0.1% 3576 1788 2214
1040 Unknown 2 0.1% 1920 960 960
38383 Addressable 2 0.1% 1532 766 815
30515 Addressable 2 0.1% 5152 2576 2576
30091 Addressable 2 0.1% 1221 610 611
30088 Addressable 2 0.1% 3717 1858 1859
21617 Ephemeral 1 0.1% 494 494 494
30380 Addressable 1 0.1% 454 454 454
22611 Ephemeral 1 0.1% 426 426 426
30079 Addressable 1 0.1% 875 875 875
22767 Ephemeral 1 0.1% 429 429 429
38500 Addressable 1 0.1% 711 711 711
30312 Addressable 1 0.1% 461 461 461
7368 Unknown 1 0.1% 1039 1039 1039
22967 Ephemeral 1 0.1% 431 431 431
39101 Addressable 1 0.1% 533 533 533
445 Encrypted group msg 1 0.1% 4130 4130 4130
10000 Mute list 1 0.1% 1722 1722 1722
30617 Addressable 1 0.1% 1089 1089 1089
22526 Ephemeral 1 0.1% 430 430 430
20787 Ephemeral 1 0.1% 926 926 926
443 Unknown 1 0.1% 800 800 800
16 Generic repost 1 0.1% 780 780 780
10011 Unknown 1 0.1% 397 397 397
22679 Ephemeral 1 0.1% 693 693 693
23042 Ephemeral 1 0.1% 429 429 429
22514 Ephemeral 1 0.1% 428 428 428
30023 Long-form article 1 0.1% 8164 8164 8164
5300 Unknown 1 0.1% 630 630 630
22605 Ephemeral 1 0.1% 692 692 692
34237 Addressable 1 0.1% 418 418 418
30618 Addressable 1 0.1% 733 733 733
38421 Addressable 1 0.1% 1069 1069 1069
10100 Unknown 1 0.1% 1036 1036 1036
30080 Addressable 1 0.1% 925 925 925
38385 Addressable 1 0.1% 1214 1214 1214
38225 Addressable 1 0.1% 1012 1012 1012
11998 Unknown 1 0.1% 465 465 465
22236 Ephemeral 1 0.1% 747 747 747
22823 Ephemeral 1 0.1% 1852 1852 1852
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 11:20:28 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 11:21:06] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 11:21:06] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.damus.io STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 11:21:26] [RELAY] wss://relay.ditto.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:21:26] [RELAY] wss://relay.ditto.pub CONNECTED
@@ -0,0 +1,423 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 40m 5s
=== Global Summary ===
Total events: 40082
Total bytes: 52985157 (50.53 MB)
Avg event size: 1322 bytes
Max event: 58662 bytes (kind 3)
Unique pubkeys: 7202
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 7469 0 0 0 0 11 OK
wss://nostr-pub.wellorder.net 697 0 0 0 0 0 OK
wss://nostr.mom 1389 0 0 0 0 0 OK
wss://nostr.oxtr.dev 1009 0 0 0 0 0 OK
wss://offchain.pub 4338 0 0 0 0 0 OK
wss://premium.primal.net 226 0 0 0 0 36 OK
wss://relay.damus.io 4929 0 0 0 0 6 OK
wss://relay.ditto.pub 5951 0 0 0 0 0 OK
wss://relay.divine.video 127 0 0 0 0 0 OK
wss://relay.momostr.pink 857 0 0 0 0 0 OK
wss://relay.mostr.pub 4182 0 0 0 0 1 OK
wss://relay.nostrplebs.com 23 0 0 0 0 1 OK
wss://relay.primal.net 8596 0 0 0 0 10 OK
wss://theforest.nostr1.com 289 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 5631 14.0% 18980562 3371 5314
30382 Addressable 4179 10.4% 1900908 455 483
22591 Ephemeral 3320 8.3% 2581724 778 1814
5 Deletion 2848 7.1% 1264487 444 1659
20001 Ephemeral 1602 4.0% 1333680 833 5870
1 Text note 1556 3.9% 1359052 873 2843
30078 App data 1118 2.8% 4202383 3759 52067
22668 Ephemeral 1014 2.5% 435006 429 429
24133 Ephemeral 965 2.4% 2468722 2558 19622
0 Profile metadata 959 2.4% 865512 903 6506
7 Reaction 766 1.9% 465618 608 1403
22551 Ephemeral 703 1.8% 305982 435 1819
22734 Ephemeral 684 1.7% 292068 427 427
38501 Addressable 583 1.5% 432057 741 744
22912 Ephemeral 582 1.5% 249096 428 428
10002 Relay list 579 1.4% 898767 1552 42385
22850 Ephemeral 543 1.4% 233490 430 430
37195 Addressable 436 1.1% 329238 755 934
25555 Ephemeral 407 1.0% 2047337 5030 16903
30383 Addressable 379 0.9% 197360 521 544
22832 Ephemeral 342 0.9% 149404 437 446
22690 Ephemeral 309 0.8% 132870 430 430
22651 Ephemeral 305 0.8% 130235 427 427
22236 Ephemeral 301 0.8% 226221 752 815
6 Repost 299 0.7% 301866 1010 9866
4515 Unknown 291 0.7% 533897 1835 16952
22601 Ephemeral 252 0.6% 107856 428 428
25050 Ephemeral 233 0.6% 102329 439 465
31991 Addressable 229 0.6% 105569 461 461
22655 Ephemeral 218 0.5% 93304 428 428
29333 Ephemeral 215 0.5% 105780 492 492
22795 Ephemeral 188 0.5% 214828 1143 1847
22676 Ephemeral 188 0.5% 80276 427 427
22808 Ephemeral 178 0.4% 84072 472 1253
22384 Ephemeral 175 0.4% 74550 426 426
31990 App handler info 167 0.4% 183247 1097 6861
22913 Ephemeral 160 0.4% 68960 431 431
13194 NWC info 156 0.4% 80184 514 514
22603 Ephemeral 155 0.4% 66030 426 426
22743 Ephemeral 155 0.4% 66340 428 428
22575 Ephemeral 154 0.4% 65450 425 425
20000 Ephemeral 151 0.4% 67354 446 1198
7368 Unknown 149 0.4% 154743 1039 1059
23049 Ephemeral 147 0.4% 63210 430 430
24242 Ephemeral 144 0.4% 83376 579 579
11998 Unknown 142 0.4% 65790 463 465
445 Encrypted group msg 140 0.3% 293740 2098 5194
22456 Ephemeral 131 0.3% 63614 486 790
20787 Ephemeral 131 0.3% 179206 1368 2478
20387 Ephemeral 128 0.3% 65792 514 514
22911 Ephemeral 125 0.3% 190126 1521 1849
22653 Ephemeral 124 0.3% 188585 1521 1848
22455 Ephemeral 124 0.3% 188540 1520 1844
22695 Ephemeral 124 0.3% 188335 1519 1850
22891 Ephemeral 122 0.3% 185731 1522 1850
1059 Gift wrap seal 122 0.3% 235480 1930 5285
22543 Ephemeral 122 0.3% 185791 1523 1846
22689 Ephemeral 119 0.3% 181177 1522 1845
22816 Ephemeral 118 0.3% 50622 429 429
22774 Ephemeral 115 0.3% 49220 428 428
23003 Ephemeral 113 0.3% 48364 428 428
22842 Ephemeral 111 0.3% 169484 1527 1848
443 Unknown 111 0.3% 163453 1473 22480
23337 Ephemeral 108 0.3% 57242 530 531
30315 User status 107 0.3% 66269 619 1166
22699 Ephemeral 106 0.3% 48256 455 1505
1985 Unknown 104 0.3% 63816 614 4833
22663 Ephemeral 101 0.3% 43228 428 428
38502 Addressable 101 0.3% 38885 385 385
23004 Ephemeral 100 0.2% 43100 431 431
4004 Unknown 100 0.2% 116900 1169 1169
3 Follow list 96 0.2% 1944563 20256 58662
4 Encrypted DM 95 0.2% 95818 1009 17018
9735 Zap receipt 94 0.2% 183437 1951 2436
22647 Ephemeral 83 0.2% 35441 427 427
31234 Addressable 81 0.2% 177493 2191 44546
5300 Unknown 80 0.2% 48788 610 744
23334 Ephemeral 75 0.2% 33681 449 454
10100 Unknown 74 0.2% 110084 1488 3826
22613 Ephemeral 74 0.2% 53289 720 1814
20004 Ephemeral 71 0.2% 37593 529 549
22631 Ephemeral 67 0.2% 61813 923 1822
20333 Ephemeral 67 0.2% 53378 797 798
22657 Ephemeral 67 0.2% 53820 803 1279
10050 DM relays 64 0.2% 27983 437 536
22545 Ephemeral 61 0.2% 26047 427 427
30023 Long-form article 56 0.1% 369281 6594 16772
30311 Addressable 55 0.1% 56530 1028 1198
1111 Comment 51 0.1% 72307 1418 2352
30166 Addressable 49 0.1% 26188 534 808
4244 Unknown 41 0.1% 23124 564 564
30088 Addressable 41 0.1% 76377 1863 1871
30091 Addressable 40 0.1% 24624 616 629
38503 Addressable 40 0.1% 22081 552 553
22782 Ephemeral 40 0.1% 36524 913 1167
22611 Ephemeral 38 0.1% 16263 428 429
22605 Ephemeral 36 0.1% 42806 1189 1849
22767 Ephemeral 35 0.1% 15015 429 429
22967 Ephemeral 34 0.1% 14654 431 431
38383 Addressable 33 0.1% 28002 849 3207
10011 Unknown 32 0.1% 13152 411 431
8108 Unknown 30 0.1% 39052 1302 1574
22623 Ephemeral 30 0.1% 24536 818 1283
30089 Chunked data 29 0.1% 370012 12759 44372
30515 Addressable 29 0.1% 74704 2576 2576
10777 Unknown 26 0.1% 12688 488 488
38385 Addressable 26 0.1% 34815 1339 1496
22679 Ephemeral 24 0.1% 28787 1199 1852
25195 Ephemeral 24 0.1% 14928 622 678
21060 Ephemeral 23 0.1% 13110 570 570
22738 Ephemeral 23 0.1% 18515 805 1252
30380 Addressable 23 0.1% 10442 454 454
33334 Addressable 22 0.1% 18282 831 831
22547 Ephemeral 22 0.1% 20278 922 1164
21617 Ephemeral 21 0.1% 10544 502 517
33301 Addressable 20 0.0% 35736 1787 2162
38500 Addressable 19 0.0% 13531 712 713
38384 Addressable 19 0.0% 14115 743 743
22526 Ephemeral 19 0.0% 8170 430 430
6300 Unknown 19 0.0% 64041 3371 6898
30443 Addressable 19 0.0% 22604 1190 1260
22919 Ephemeral 18 0.0% 32466 1804 1849
22724 Ephemeral 18 0.0% 13222 735 1253
22789 Ephemeral 17 0.0% 28521 1678 3267
1002 Unknown 17 0.0% 16010 942 981
3434 Unknown 17 0.0% 11585 681 705
22507 Ephemeral 16 0.0% 9927 620 1163
20384 Ephemeral 16 0.0% 10223 639 639
30080 Addressable 16 0.0% 15048 940 987
22672 Ephemeral 16 0.0% 24987 1562 1847
22844 Ephemeral 15 0.0% 6435 429 429
4245 Unknown 15 0.0% 6660 444 444
4243 Unknown 15 0.0% 8460 564 564
30009 Addressable 15 0.0% 6949 463 479
22514 Ephemeral 14 0.0% 5992 428 428
14 Unknown 14 0.0% 15322 1094 1551
3927 Unknown 14 0.0% 7484 535 544
23042 Ephemeral 14 0.0% 6006 429 429
10000 Mute list 14 0.0% 32097 2293 8671
30618 Addressable 13 0.0% 25947 1996 8739
1040 Unknown 13 0.0% 13272 1021 1048
31510 Addressable 13 0.0% 12731 979 982
30617 Addressable 12 0.0% 13112 1093 1146
25196 Ephemeral 12 0.0% 19580 1632 2214
38225 Addressable 12 0.0% 11742 978 1006
39101 Addressable 11 0.0% 5863 533 533
26428 Ephemeral 10 0.0% 8765 876 877
30213 Addressable 10 0.0% 6224 622 632
26429 Ephemeral 10 0.0% 4910 491 491
30000 Addressable 9 0.0% 17509 1945 5660
22639 Ephemeral 9 0.0% 3852 428 428
31339 Addressable 9 0.0% 13230 1470 1470
30312 Addressable 8 0.0% 3555 444 461
9321 Unknown 7 0.0% 16541 2363 2363
7403 Unknown 7 0.0% 3435 491 491
1010 Unknown 7 0.0% 5098 728 748
16 Generic repost 7 0.0% 5460 780 780
7375 Unknown 7 0.0% 8120 1160 1160
22839 Ephemeral 7 0.0% 2996 428 428
7376 Unknown 7 0.0% 5817 831 831
3079 Unknown 6 0.0% 5454 909 909
22658 Ephemeral 6 0.0% 6269 1045 1813
30090 Addressable 5 0.0% 7575 1515 1515
34236 Addressable 5 0.0% 61577 12315 21112
22120 Ephemeral 5 0.0% 5493 1099 1133
22119 Ephemeral 5 0.0% 4227 845 846
23194 NWC request 5 0.0% 2554 511 534
1984 Report 5 0.0% 2714 543 662
31000 Addressable 5 0.0% 3024 605 616
1078 Unknown 5 0.0% 70534 14107 14226
10003 Bookmarks 5 0.0% 15698 3140 4954
1000 Unknown 4 0.0% 6474 1618 1782
30087 Addressable 4 0.0% 29300 7325 7325
38111 Addressable 4 0.0% 2996 749 752
25055 Ephemeral 4 0.0% 6286 1572 1788
30421 Addressable 4 0.0% 4079 1020 1028
30079 Addressable 4 0.0% 3500 875 875
30815 Addressable 4 0.0% 150136 37534 37534
13302 Unknown 4 0.0% 30097 7524 22303
22865 Ephemeral 4 0.0% 2785 696 699
10012 Unknown 3 0.0% 1388 463 622
39015 Addressable 3 0.0% 2088 696 696
30787 Addressable 3 0.0% 4118 1373 1374
10001 Pinned notes 3 0.0% 1758 586 586
15129 Unknown 3 0.0% 1365 455 455
37779 Addressable 3 0.0% 1569 523 523
8 Unknown 3 0.0% 1527 509 509
30003 Addressable 3 0.0% 1167 389 389
31512 Addressable 3 0.0% 10234 3411 3414
39050 Addressable 3 0.0% 5529 1843 1843
31237 Addressable 3 0.0% 2439 813 821
30554 Addressable 3 0.0% 1827 609 609
22778 Ephemeral 3 0.0% 4059 1353 1847
30402 Addressable 3 0.0% 2036 679 680
10063 Unknown 3 0.0% 1639 546 627
1631 Unknown 2 0.0% 2620 1310 1310
1315 Unknown 2 0.0% 990 495 495
22760 Ephemeral 2 0.0% 3624 1812 1814
30495 Addressable 2 0.0% 4074 2037 2037
10051 Unknown 2 0.0% 878 439 439
13196 Unknown 2 0.0% 1312 656 656
38421 Addressable 2 0.0% 2140 1070 1070
23333 Ephemeral 2 0.0% 1636 818 818
2333 Unknown 2 0.0% 1812 906 906
24913 Ephemeral 2 0.0% 1164 582 582
22882 Ephemeral 2 0.0% 2000 1000 1164
22617 Ephemeral 2 0.0% 852 426 426
22729 Ephemeral 2 0.0% 1860 930 930
22733 Ephemeral 2 0.0% 3330 1665 1848
23013 Ephemeral 2 0.0% 2965 1482 1485
9 Unknown 2 0.0% 4085 2042 2157
31986 Addressable 2 0.0% 1182 591 591
1063 File metadata 1 0.0% 909 909 909
1060 Unknown 1 0.0% 1207 1207 1207
10312 Unknown 1 0.0% 472 472 472
37368 Addressable 1 0.0% 1236 1236 1236
15750 Unknown 1 0.0% 626 626 626
22598 Ephemeral 1 0.0% 1666 1666 1666
17375 Unknown 1 0.0% 606 606 606
22496 Ephemeral 1 0.0% 1518 1518 1518
20200 Ephemeral 1 0.0% 491 491 491
39019 Addressable 1 0.0% 961 961 961
34238 Addressable 1 0.0% 920 920 920
22732 Ephemeral 1 0.0% 1334 1334 1334
20201 Ephemeral 1 0.0% 491 491 491
12222 Unknown 1 0.0% 579 579 579
31337 Addressable 1 0.0% 818 818 818
23071 Ephemeral 1 0.0% 1910 1910 1910
33333 Addressable 1 0.0% 2346 2346 2346
20 Picture 1 0.0% 445 445 445
1808 Unknown 1 0.0% 683 683 683
30113 Addressable 1 0.0% 770 770 770
34235 Addressable 1 0.0% 1501 1501 1501
10377 Unknown 1 0.0% 664 664 664
11111 Unknown 1 0.0% 382 382 382
10133 Unknown 1 0.0% 499 499 499
30081 Addressable 1 0.0% 1370 1370 1370
10009 Unknown 1 0.0% 422 422 422
30005 Addressable 1 0.0% 9671 9671 9671
10443 Unknown 1 0.0% 676 676 676
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 11:21:56 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 11:22:33] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 11:22:33] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.damus.io STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 11:23:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:06] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:08] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:08] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:09] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:23:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:23:45] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:23:45] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:24:08] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:24:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:26:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:10] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:14] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:15] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:15] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:26:15] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:26:28] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:29] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:26:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:26:43] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:43] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:44] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:45] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:47] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:48] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:49] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:50] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:53] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:53] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:27:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:17] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:27:31] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:32] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 11:27:58] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:28:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:12] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:15] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:28:15] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:28:15] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:16] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:20] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:22] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:22] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:24] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:25] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:44] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:45] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:45] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:28:45] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:28:46] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:47] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:51] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:51] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:56] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:56] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:29:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:29:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:29:59] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:00] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:01] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:01] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:02] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:03] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:03] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:04] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:07] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:08] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:14] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:15] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:18] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:19] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:30:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:30:20] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:22] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:23] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:25] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:26] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:30:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:31:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:31:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:31:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:31:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:32:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:32:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:32:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:32:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:32:46] [RELAY] wss://relay.damus.io DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:32:52] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:33:03] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:33:13] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:33:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:33:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:33:54] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:33:54] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:34:12] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:34:12] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:34:24] [RELAY] wss://relay.damus.io DISCONNECTED:
[2026-08-02 11:34:24] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:35:13] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:35:13] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:35:24] [RELAY] wss://relay.damus.io DISCONNECTED:
[2026-08-02 11:35:24] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:36:18] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:36:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:36:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:36:28] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:36:49] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:36:49] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:37:14] [RELAY] wss://relay.nostrplebs.com DISCONNECTED:
[2026-08-02 11:37:14] [RELAY] wss://relay.nostrplebs.com CONNECTED
[2026-08-02 11:37:19] [RELAY] wss://premium.primal.net DISCONNECTED:
[2026-08-02 11:37:19] [RELAY] wss://premium.primal.net CONNECTED
@@ -0,0 +1,646 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 58m 5s
=== Global Summary ===
Total events: 140345
Total bytes: 182488517 (174.03 MB)
Avg event size: 1300 bytes
Max event: 62825 bytes (kind 1)
Unique pubkeys: 26918
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 25038 0 0 0 0 54 OK
wss://nostr-pub.wellorder.net 3608 0 0 0 0 0 OK
wss://nostr.mom 8692 0 0 0 0 0 OK
wss://nostr.oxtr.dev 4360 0 0 0 0 7 OK
wss://offchain.pub 17857 0 0 0 0 0 OK
wss://premium.primal.net 171 0 0 0 0 26 OK
wss://relay.damus.io 21539 0 0 0 0 0 OK
wss://relay.ditto.pub 22251 0 0 0 0 7 OK
wss://relay.divine.video 281 0 0 0 0 0 OK
wss://relay.momostr.pink 1812 0 0 0 0 7 OK
wss://relay.mostr.pub 20917 0 0 0 0 10 OK
wss://relay.nostrplebs.com 23 0 0 0 0 0 OK
wss://relay.primal.net 12945 0 0 0 0 57 OK
wss://theforest.nostr1.com 851 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 22589 16.1% 76886982 3404 22354
30382 Addressable 15147 10.8% 6854204 453 484
5 Deletion 10634 7.6% 4652532 438 955
20001 Ephemeral 4553 3.2% 2990229 657 26578
22591 Ephemeral 4464 3.2% 3384073 758 1230
1 Text note 4307 3.1% 4175360 969 62825
22668 Ephemeral 4214 3.0% 1807801 429 429
22466 Ephemeral 3541 2.5% 1519089 429 429
30383 Addressable 3125 2.2% 1507650 482 550
0 Profile metadata 2878 2.1% 2533064 880 9187
22551 Ephemeral 2857 2.0% 1233111 432 1160
30078 App data 2566 1.8% 8335901 3249 55090
38501 Addressable 2349 1.7% 1740867 741 744
22912 Ephemeral 1869 1.3% 802701 429 3197
22684 Ephemeral 1859 1.3% 803991 432 3193
22734 Ephemeral 1629 1.2% 695583 427 427
10002 Relay list 1604 1.1% 4950855 3087 42385
7 Reaction 1435 1.0% 847949 591 1287
29333 Ephemeral 1408 1.0% 690797 491 492
24133 Ephemeral 1375 1.0% 1890802 1375 38738
37195 Addressable 1338 1.0% 1004351 751 934
25555 Ephemeral 1181 0.8% 5929927 5021 17075
22690 Ephemeral 1081 0.8% 467584 433 3184
22601 Ephemeral 990 0.7% 423742 428 429
22850 Ephemeral 947 0.7% 407208 430 430
4515 Unknown 868 0.6% 1923082 2216 55180
23334 Ephemeral 859 0.6% 374353 436 439
22655 Ephemeral 821 0.6% 351388 428 428
6 Repost 811 0.6% 771135 951 10128
22795 Ephemeral 778 0.6% 1060284 1363 3201
4 Encrypted DM 767 0.5% 725743 946 19618
22720 Ephemeral 749 0.5% 1079163 1441 3249
22876 Ephemeral 724 0.5% 308478 426 431
22676 Ephemeral 696 0.5% 297193 427 428
22913 Ephemeral 678 0.5% 294988 435 3201
22689 Ephemeral 663 0.5% 1094513 1651 5926
22927 Ephemeral 607 0.4% 1017238 1676 1876
22236 Ephemeral 595 0.4% 446548 751 814
22911 Ephemeral 569 0.4% 951997 1673 1880
22943 Ephemeral 563 0.4% 249401 443 1658
22603 Ephemeral 543 0.4% 231317 426 427
22743 Ephemeral 542 0.4% 231978 428 430
22695 Ephemeral 538 0.4% 871272 1619 1850
1059 Gift wrap seal 535 0.4% 2557163 4780 55121
22842 Ephemeral 528 0.4% 882417 1671 1853
24242 Ephemeral 527 0.4% 305133 579 579
13194 NWC info 505 0.4% 259258 513 514
22730 Ephemeral 474 0.3% 418919 884 1517
11998 Unknown 449 0.3% 208257 464 465
23337 Ephemeral 446 0.3% 252660 567 586
7368 Unknown 443 0.3% 459822 1038 1059
22816 Ephemeral 410 0.3% 183597 448 3196
30315 User status 406 0.3% 280885 692 1166
22456 Ephemeral 403 0.3% 196962 489 790
22815 Ephemeral 386 0.3% 620350 1607 3199
20387 Ephemeral 368 0.3% 189152 514 514
20000 Ephemeral 351 0.3% 237048 675 17065
5300 Unknown 350 0.2% 213715 611 701
22774 Ephemeral 349 0.2% 149372 428 428
31990 App handler info 343 0.2% 389821 1137 6861
3 Follow list 325 0.2% 4492770 13824 58982
23003 Ephemeral 317 0.2% 135676 428 428
38502 Addressable 295 0.2% 113575 385 385
1985 Unknown 287 0.2% 189598 661 4834
22807 Ephemeral 279 0.2% 119596 429 430
25050 Ephemeral 272 0.2% 113448 417 1362
4004 Unknown 270 0.2% 315630 1169 1169
20004 Ephemeral 233 0.2% 124155 533 549
35000 Addressable 208 0.1% 160992 774 774
22613 Ephemeral 208 0.1% 143395 689 1134
22592 Ephemeral 201 0.1% 93351 464 3193
20333 Ephemeral 197 0.1% 156942 797 798
31950 Addressable 196 0.1% 170818 872 937
22632 Ephemeral 195 0.1% 85266 437 3204
9735 Zap receipt 193 0.1% 370520 1920 4167
10100 Unknown 188 0.1% 279911 1489 3826
30311 Addressable 187 0.1% 202343 1082 1197
24913 Ephemeral 179 0.1% 79157 442 582
23004 Ephemeral 179 0.1% 77149 431 431
22854 Ephemeral 174 0.1% 137235 789 1660
30384 Addressable 166 0.1% 85437 515 556
22891 Ephemeral 164 0.1% 235679 1437 3243
10050 DM relays 160 0.1% 72634 454 592
30023 Long-form article 144 0.1% 954406 6628 13594
6300 Unknown 144 0.1% 880738 6116 17462
445 Encrypted group msg 140 0.1% 350204 2501 4134
10000 Mute list 136 0.1% 274284 2017 27742
22693 Ephemeral 130 0.1% 101515 781 2619
30166 Addressable 128 0.1% 66754 522 808
4244 Unknown 127 0.1% 71628 564 564
38503 Addressable 125 0.1% 68916 551 552
20787 Ephemeral 124 0.1% 181428 1463 2478
22455 Ephemeral 112 0.1% 183137 1635 1847
22616 Ephemeral 111 0.1% 98460 887 3453
22543 Ephemeral 107 0.1% 172800 1615 1849
22885 Ephemeral 106 0.1% 102539 967 3524
22672 Ephemeral 104 0.1% 174090 1674 1849
22717 Ephemeral 99 0.1% 120874 1221 1849
22929 Ephemeral 90 0.1% 88203 980 1517
10777 Unknown 89 0.1% 43507 489 489
22829 Ephemeral 89 0.1% 43243 486 3200
30088 Addressable 87 0.1% 162094 1863 1871
30091 Addressable 85 0.1% 52311 615 629
21584 Ephemeral 83 0.1% 36413 439 439
38385 Addressable 82 0.1% 111350 1358 1496
22864 Ephemeral 82 0.1% 56300 687 1223
39101 Addressable 81 0.1% 43173 533 533
22751 Ephemeral 81 0.1% 73608 909 4083
1111 Comment 79 0.1% 93490 1183 2457
3927 Unknown 78 0.1% 41585 533 544
10011 Unknown 77 0.1% 33123 430 431
21060 Ephemeral 74 0.1% 42180 570 570
21617 Ephemeral 73 0.1% 36119 495 495
33334 Addressable 70 0.0% 58170 831 831
30380 Addressable 66 0.0% 29964 454 454
38383 Addressable 66 0.0% 59966 909 3211
38384 Addressable 62 0.0% 46066 743 743
38500 Addressable 60 0.0% 42716 712 713
20384 Ephemeral 59 0.0% 37701 639 639
30089 Chunked data 53 0.0% 614992 11604 44372
1002 Unknown 53 0.0% 48203 909 1011
22840 Ephemeral 52 0.0% 22285 429 430
23066 Ephemeral 50 0.0% 43751 875 1165
22526 Ephemeral 50 0.0% 21469 429 430
22405 Ephemeral 47 0.0% 20886 444 445
23042 Ephemeral 44 0.0% 18876 429 429
22514 Ephemeral 44 0.0% 18832 428 428
22748 Ephemeral 44 0.0% 18874 429 429
31510 Addressable 43 0.0% 42075 978 982
3079 Unknown 42 0.0% 38178 909 909
4243 Unknown 42 0.0% 23688 564 564
22862 Ephemeral 41 0.0% 17578 429 429
22475 Ephemeral 40 0.0% 43786 1095 3923
22721 Ephemeral 40 0.0% 23480 587 1842
22674 Ephemeral 39 0.0% 17992 461 1661
4245 Unknown 39 0.0% 17316 444 444
30080 Addressable 39 0.0% 37674 966 1011
30443 Addressable 38 0.0% 46231 1217 1547
30515 Addressable 38 0.0% 201552 5304 5304
22212 Ephemeral 37 0.0% 16440 444 445
10003 Bookmarks 37 0.0% 420277 11359 56461
38225 Addressable 35 0.0% 33592 960 1021
30815 Addressable 34 0.0% 1276156 37534 37534
1984 Report 34 0.0% 22174 652 667
22753 Ephemeral 33 0.0% 40204 1218 3766
30618 Addressable 32 0.0% 45089 1409 8804
1040 Unknown 32 0.0% 36596 1144 5164
3434 Unknown 30 0.0% 20382 679 705
7000 Unknown 28 0.0% 21752 777 822
30385 Addressable 28 0.0% 22344 798 981
33301 Addressable 27 0.0% 45586 1688 2162
16 Generic repost 27 0.0% 21778 807 1604
22119 Ephemeral 25 0.0% 21137 845 846
22120 Ephemeral 25 0.0% 27207 1088 1128
24421 Ephemeral 24 0.0% 12384 516 516
22525 Ephemeral 24 0.0% 42802 1783 4259
22756 Ephemeral 24 0.0% 32806 1367 3198
31124 Addressable 23 0.0% 28396 1235 1445
22733 Ephemeral 23 0.0% 15376 669 1224
30617 Addressable 23 0.0% 25047 1089 1089
22649 Ephemeral 22 0.0% 9459 430 430
22680 Ephemeral 21 0.0% 14310 681 3193
22874 Ephemeral 21 0.0% 24675 1175 1850
31339 Addressable 21 0.0% 30870 1470 1470
30000 Addressable 19 0.0% 16519 869 3185
22688 Ephemeral 19 0.0% 18248 960 3195
31991 Addressable 19 0.0% 8759 461 461
39842 Addressable 18 0.0% 19919 1107 1385
10312 Unknown 18 0.0% 9219 512 559
7403 Unknown 17 0.0% 8336 490 491
30280 Addressable 17 0.0% 8721 513 513
14 Unknown 16 0.0% 17438 1090 1531
31000 Addressable 16 0.0% 9716 607 616
22645 Ephemeral 16 0.0% 9285 580 1838
2333 Unknown 15 0.0% 10074 672 722
34236 Addressable 15 0.0% 211139 14076 26491
30079 Addressable 15 0.0% 47271 3151 4574
23333 Ephemeral 15 0.0% 8754 584 634
23194 NWC request 15 0.0% 8952 597 1086
22778 Ephemeral 15 0.0% 6421 428 429
38111 Addressable 14 0.0% 10495 750 752
22747 Ephemeral 13 0.0% 8895 684 1657
25000 Ephemeral 13 0.0% 23498 1808 2214
22506 Ephemeral 13 0.0% 23855 1835 1841
1000 Unknown 13 0.0% 21880 1683 1790
26428 Ephemeral 12 0.0% 10516 876 877
23195 NWC response 12 0.0% 9184 765 1183
1078 Unknown 12 0.0% 169653 14138 14241
30554 Addressable 12 0.0% 7292 608 608
22361 Ephemeral 11 0.0% 4708 428 428
30421 Addressable 11 0.0% 11427 1039 1066
30085 Addressable 11 0.0% 7227 657 659
10063 Unknown 11 0.0% 5982 544 627
22402 Ephemeral 11 0.0% 4683 426 443
39015 Addressable 11 0.0% 7656 696 696
11125 Unknown 10 0.0% 10262 1026 1916
30113 Addressable 10 0.0% 6987 699 770
33051 Addressable 10 0.0% 5206 521 522
30087 Addressable 9 0.0% 65925 7325 7325
30213 Addressable 9 0.0% 5662 629 637
1018 Unknown 8 0.0% 4500 562 677
30090 Addressable 8 0.0% 12120 1515 1515
1050 Unknown 8 0.0% 3496 437 437
22619 Ephemeral 8 0.0% 11165 1396 1842
10051 Unknown 7 0.0% 3205 458 489
26429 Ephemeral 7 0.0% 3437 491 491
38421 Addressable 7 0.0% 7485 1069 1070
39019 Addressable 6 0.0% 5766 961 961
9842 Unknown 6 0.0% 6742 1124 1260
20200 Ephemeral 6 0.0% 2946 491 491
25742 Ephemeral 6 0.0% 11756 1959 7334
30081 Addressable 6 0.0% 8220 1370 1370
20 Picture 6 0.0% 4558 760 852
20201 Ephemeral 6 0.0% 2946 491 491
22722 Ephemeral 5 0.0% 7756 1551 1660
7336 Unknown 5 0.0% 3280 656 656
22825 Ephemeral 5 0.0% 5660 1132 1848
13196 Unknown 5 0.0% 3280 656 656
9841 Unknown 5 0.0% 14029 2806 4468
23336 Ephemeral 5 0.0% 3285 657 657
1063 File metadata 5 0.0% 4593 919 941
31344 Addressable 4 0.0% 2500 625 625
31986 Addressable 4 0.0% 2372 593 593
22432 Ephemeral 4 0.0% 5412 1353 1843
31985 Addressable 4 0.0% 2534 634 667
10015 Unknown 3 0.0% 11266 3755 4578
1010 Unknown 3 0.0% 2505 835 1152
37779 Addressable 3 0.0% 1569 523 523
21734 Ephemeral 3 0.0% 1131 377 377
22671 Ephemeral 3 0.0% 1281 427 427
31343 Addressable 2 0.0% 53115 26558 45456
16767 Unknown 2 0.0% 1974 987 987
1058 Unknown 2 0.0% 1186 593 593
22724 Ephemeral 2 0.0% 2103 1052 1674
30005 Addressable 2 0.0% 11351 5676 9817
21724 Ephemeral 2 0.0% 754 377 377
20177 Ephemeral 2 0.0% 851 426 426
2003 Unknown 2 0.0% 8711 4356 7015
30003 Addressable 2 0.0% 778 389 389
25582 Ephemeral 2 0.0% 4500 2250 3690
38422 Addressable 2 0.0% 60198 30099 30212
22387 Ephemeral 2 0.0% 887 444 444
1618 Unknown 1 0.0% 2168 2168 2168
10001 Pinned notes 1 0.0% 710 710 710
30758 Addressable 1 0.0% 436 436 436
32267 Addressable 1 0.0% 1693 1693 1693
1069 Unknown 1 0.0% 757 757 757
31345 Addressable 1 0.0% 8666 8666 8666
21840 Ephemeral 1 0.0% 377 377 377
21764 Ephemeral 1 0.0% 377 377 377
31353 Addressable 1 0.0% 737 737 737
1808 Unknown 1 0.0% 1696 1696 1696
31633 Addressable 1 0.0% 592 592 592
30066 Addressable 1 0.0% 675 675 675
30063 Addressable 1 0.0% 2891 2891 2891
21711 Ephemeral 1 0.0% 377 377 377
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 15:24:51 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 15:25:27] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 15:25:27] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.damus.io STATUS: ?
[2026-08-02 15:25:27] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 15:25:39] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 15:26:24] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:26:24] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:26:46] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:26:46] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:27:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:27:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:27:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:27:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:28:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:28:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:46] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:28:46] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:29:16] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:29:16] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:29:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:29:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:30:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:30:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:30:16] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:30:16] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:31:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:31:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:31:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:31:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:32:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:32:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:32:37] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:32:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:33:19] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:33:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:33:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:33:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:33:49] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:33:49] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:34:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:34:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:34:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:34:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:35:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:11] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:11] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:35:12] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:13] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:35:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:35:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:41] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:35:41] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:35:54] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:55] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:37:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:37:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:37:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:37:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:38:11] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:38:11] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:38:34] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:38:35] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:39:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:39:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:39:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:18] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:39:32] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:32] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:39:34] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:34] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:39:59] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:40:07] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:40:07] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:40:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:40:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:40:40] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:40:41] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:41:29] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:41:29] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:41:39] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:41:40] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:41:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:41:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:42:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:42:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:42:51] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:42:51] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:44:41] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:44:41] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:45:00] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:45:01] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:45:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:45:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:45:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:45:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:45:26] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:45:27] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:45:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:45:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:46:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:08] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:46:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:46:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:46:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:46:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:46:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:46:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:46:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:46:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:46:51] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:46:52] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:47:00] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:01] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:01] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:02] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:47:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:47:07] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:08] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:08] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:09] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:18] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:47:18] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:47:33] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:48:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:48:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:48:41] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:48:42] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:49:03] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:49:03] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:49:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:49:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:49:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:49:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:50:02] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:50:02] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:50:05] [RELAY] wss://relay.ditto.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:50:05] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:50:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:50:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:51:18] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:51:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:51:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:51:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:52:00] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:52:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:52:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:52:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:52:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:52:33] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:34] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:52:34] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:35] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:53:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:53:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:53:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:53:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:53:30] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:53:30] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:53:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:53:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:54:19] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:54:54] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:55] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:56] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:56] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:57] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:58] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:58] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:59] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:01] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:02] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:02] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:04] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:04] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:55:04] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:55:04] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:05] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:06] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:07] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:08] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:09] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:55:11] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:12] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:55:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:55:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:55:36] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:37] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:55:50] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:50] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:56:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:56:10] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:56:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:56:13] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:57:22] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:57:23] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:58:04] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:58:04] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:00:01] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:00:01] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:00:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:00:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:00:54] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:00:54] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:01:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:01:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:01:48] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:01:48] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:01:55] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:01:55] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:02:29] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:02:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:02:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:02:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:03:01] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:03:01] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:03:29] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:03:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:03:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:03:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:03:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:03:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:04:29] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:04:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:04:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:04:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:05:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:13] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:15] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:16] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:19] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:21] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:21] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:24] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:25] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:05:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:06:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:06:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:08:00] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:08:01] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:08:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:08:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:08:42] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:08:42] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:10:20] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:10:21] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:11:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:11:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:11:26] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:27] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:30] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:30] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:32] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:32] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:39] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:11:39] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:11:46] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:47] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:12:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:12:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:13:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:13:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:13:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:13:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:13:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:13:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:14:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:14:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:14:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:14:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:15:16] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:17] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:15:23] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:24] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:15:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:15:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:15:41] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:42] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:16:25] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:26] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:16:28] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:28] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:16:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:16:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:16:36] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:36] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 16:17:02] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:17:02] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:17:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:17:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:18:02] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:18:02] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:18:08] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:18:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:18:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:18:49] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:49] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:18:55] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:56] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:19:37] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:19:37] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:19:40] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:19:40] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:20:37] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:20:37] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:21:07] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:21:07] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:21:10] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:21:10] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:21:14] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:21:15] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:21:56] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:21:56] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:22:26] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:22:26] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:22:55] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:22:55] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:23:32] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:23:33] [RELAY] wss://premium.primal.net CONNECTED
@@ -0,0 +1,243 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 8m 23s
=== Global Summary ===
Total events: 7884
Total bytes: 12205231 (11.64 MB)
Avg event size: 1548 bytes
Max event: 56750 bytes (kind 3)
Unique pubkeys: 2301
Relays: 1
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 7884 0 0 0 0 10 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
1059 Gift wrap seal 871 11.0% 1657832 1903 55121
21059 Gift wrap (NIP-59) 740 9.4% 2308644 3120 22354
30078 App data 622 7.9% 2651879 4263 55090
30383 Addressable 520 6.6% 278610 536 548
20001 Ephemeral 386 4.9% 403111 1044 2026
22734 Ephemeral 357 4.5% 152451 427 428
1 Text note 303 3.8% 303990 1003 4648
30382 Addressable 288 3.7% 137207 476 484
0 Profile metadata 189 2.4% 140594 744 2012
29333 Ephemeral 165 2.1% 81180 492 492
10002 Relay list 158 2.0% 571188 3615 42385
22236 Ephemeral 158 2.0% 120802 765 801
5 Deletion 154 2.0% 73847 480 664
4515 Unknown 154 2.0% 463562 3010 38797
22743 Ephemeral 134 1.7% 57352 428 428
7 Reaction 113 1.4% 62452 553 727
24133 Ephemeral 102 1.3% 132440 1298 7334
22684 Ephemeral 99 1.3% 42669 431 431
22850 Ephemeral 94 1.2% 40420 430 430
22838 Ephemeral 88 1.1% 37752 429 429
22774 Ephemeral 84 1.1% 35952 428 428
23003 Ephemeral 84 1.1% 35952 428 428
22876 Ephemeral 83 1.1% 35358 426 426
22611 Ephemeral 75 1.0% 32175 429 429
25555 Ephemeral 69 0.9% 281319 4077 16987
4 Encrypted DM 66 0.8% 72028 1091 17018
22613 Ephemeral 63 0.8% 43489 690 692
25050 Ephemeral 61 0.8% 24461 401 401
11998 Unknown 61 0.8% 28269 463 465
443 Unknown 49 0.6% 68056 1389 22480
30301 Addressable 47 0.6% 53284 1134 1912
3927 Unknown 47 0.6% 25051 533 533
20387 Ephemeral 44 0.6% 22616 514 514
22761 Ephemeral 44 0.6% 18832 428 428
13194 NWC info 43 0.5% 22043 513 514
20004 Ephemeral 42 0.5% 22266 530 549
5300 Unknown 42 0.5% 25437 606 637
22546 Ephemeral 39 0.5% 16614 426 426
22714 Ephemeral 37 0.5% 15836 428 428
30166 Addressable 35 0.4% 17904 512 589
9735 Zap receipt 35 0.4% 70344 2010 3538
23334 Ephemeral 35 0.4% 15084 431 432
1985 Unknown 34 0.4% 16232 477 483
22816 Ephemeral 33 0.4% 14157 429 429
21050 Ephemeral 33 0.4% 30862 935 1874
30315 User status 32 0.4% 21166 661 925
445 Encrypted group msg 32 0.4% 120956 3780 9346
22698 Ephemeral 32 0.4% 13664 427 427
4004 Unknown 32 0.4% 37408 1169 1169
38501 Addressable 31 0.4% 23034 743 744
31990 App handler info 25 0.3% 9675 387 387
10100 Unknown 24 0.3% 27687 1154 1589
20333 Ephemeral 24 0.3% 19120 797 798
3 Follow list 23 0.3% 328284 14273 56750
24913 Ephemeral 22 0.3% 9700 441 493
30311 Addressable 21 0.3% 22061 1051 1197
6 Repost 21 0.3% 49793 2371 12925
10050 DM relays 19 0.2% 8172 430 450
22712 Ephemeral 18 0.2% 7704 428 428
22808 Ephemeral 17 0.2% 7293 429 429
10000 Mute list 16 0.2% 26659 1666 12630
4244 Unknown 15 0.2% 8460 564 564
23025 Ephemeral 15 0.2% 6465 431 431
31234 Addressable 14 0.2% 24051 1718 2268
38503 Addressable 13 0.2% 7174 552 552
22630 Ephemeral 12 0.2% 11762 980 1654
37195 Addressable 12 0.2% 8752 729 934
6300 Unknown 12 0.2% 77247 6437 17248
10777 Unknown 12 0.2% 5861 488 489
30091 Addressable 11 0.1% 6763 615 629
20000 Ephemeral 11 0.1% 3806 346 346
30515 Addressable 11 0.1% 58344 5304 5304
30088 Addressable 11 0.1% 20483 1862 1871
22361 Ephemeral 10 0.1% 4439 444 445
30280 Addressable 9 0.1% 4617 513 513
22902 Ephemeral 9 0.1% 3861 429 429
22506 Ephemeral 8 0.1% 3416 427 427
7368 Unknown 8 0.1% 8304 1038 1040
22645 Ephemeral 8 0.1% 3432 429 429
38500 Addressable 8 0.1% 5696 712 713
33334 Addressable 8 0.1% 6648 831 831
30380 Addressable 8 0.1% 3632 454 454
22688 Ephemeral 7 0.1% 2989 427 427
22656 Ephemeral 7 0.1% 3017 431 431
22316 Ephemeral 7 0.1% 3106 444 444
30089 Chunked data 6 0.1% 123676 20613 44372
22456 Ephemeral 6 0.1% 3140 523 790
22739 Ephemeral 6 0.1% 2580 430 430
22882 Ephemeral 6 0.1% 5292 882 1449
22862 Ephemeral 6 0.1% 2574 429 429
1111 Comment 6 0.1% 7227 1204 1698
4245 Unknown 5 0.1% 2220 444 444
30512 Addressable 5 0.1% 7504 1501 1506
22641 Ephemeral 5 0.1% 2145 429 429
22586 Ephemeral 5 0.1% 2135 427 427
4243 Unknown 5 0.1% 2820 564 564
22835 Ephemeral 5 0.1% 2145 429 429
22620 Ephemeral 5 0.1% 2145 429 429
22298 Ephemeral 5 0.1% 2218 444 444
22852 Ephemeral 5 0.1% 2140 428 428
22726 Ephemeral 5 0.1% 2150 430 430
22362 Ephemeral 5 0.1% 2218 444 444
22772 Ephemeral 5 0.1% 4051 810 1132
22805 Ephemeral 4 0.1% 1712 428 428
9734 Zap request 4 0.1% 3756 939 1033
22200 Ephemeral 4 0.1% 1774 444 444
31510 Addressable 4 0.1% 3922 980 982
22570 Ephemeral 4 0.1% 1716 429 429
25195 Ephemeral 4 0.1% 2712 678 678
22752 Ephemeral 3 0.0% 4047 1349 3191
31339 Addressable 3 0.0% 4410 1470 1470
22949 Ephemeral 3 0.0% 1287 429 429
22358 Ephemeral 3 0.0% 1331 444 444
22474 Ephemeral 3 0.0% 1330 443 444
22185 Ephemeral 3 0.0% 1330 443 444
22936 Ephemeral 3 0.0% 1290 430 430
25196 Ephemeral 3 0.0% 4938 1646 2214
22439 Ephemeral 3 0.0% 1330 443 444
22824 Ephemeral 3 0.0% 1281 427 427
22270 Ephemeral 3 0.0% 1330 443 444
22263 Ephemeral 3 0.0% 1330 443 444
22755 Ephemeral 3 0.0% 1284 428 428
10051 Unknown 3 0.0% 1317 439 439
34775 Addressable 3 0.0% 7599 2533 2561
30300 Addressable 3 0.0% 5502 1834 1834
22234 Ephemeral 3 0.0% 1330 443 444
7403 Unknown 2 0.0% 978 489 489
22626 Ephemeral 2 0.0% 1916 958 1486
30079 Addressable 2 0.0% 1750 875 875
15750 Unknown 2 0.0% 1242 621 626
23337 Ephemeral 2 0.0% 1027 514 514
30815 Addressable 2 0.0% 75068 37534 37534
30213 Addressable 2 0.0% 1264 632 632
22716 Ephemeral 2 0.0% 854 427 427
22492 Ephemeral 2 0.0% 887 444 444
30000 Addressable 2 0.0% 1646 823 823
22463 Ephemeral 2 0.0% 887 444 444
22304 Ephemeral 2 0.0% 887 444 444
10003 Bookmarks 2 0.0% 20016 10008 15003
30443 Addressable 2 0.0% 2572 1286 1312
22233 Ephemeral 2 0.0% 886 443 443
22275 Ephemeral 2 0.0% 887 444 444
30087 Addressable 2 0.0% 11238 5619 7325
20078 Ephemeral 2 0.0% 1458 729 729
22057 Ephemeral 2 0.0% 887 444 444
22251 Ephemeral 2 0.0% 887 444 444
22181 Ephemeral 2 0.0% 887 444 444
22678 Ephemeral 2 0.0% 1388 694 694
22747 Ephemeral 2 0.0% 858 429 429
22410 Ephemeral 2 0.0% 887 444 444
22334 Ephemeral 2 0.0% 887 444 444
22249 Ephemeral 2 0.0% 887 444 444
22258 Ephemeral 2 0.0% 887 444 444
30090 Addressable 2 0.0% 2588 1294 1515
10312 Unknown 1 0.0% 472 472 472
22853 Ephemeral 1 0.0% 1673 1673 1673
22303 Ephemeral 1 0.0% 443 443 443
22306 Ephemeral 1 0.0% 443 443 443
20200 Ephemeral 1 0.0% 491 491 491
22132 Ephemeral 1 0.0% 443 443 443
20201 Ephemeral 1 0.0% 491 491 491
22245 Ephemeral 1 0.0% 443 443 443
22352 Ephemeral 1 0.0% 443 443 443
22240 Ephemeral 1 0.0% 443 443 443
34236 Addressable 1 0.0% 931 931 931
17 Unknown 1 0.0% 610 610 610
22286 Ephemeral 1 0.0% 443 443 443
13196 Unknown 1 0.0% 656 656 656
20242 Ephemeral 1 0.0% 346 346 346
7376 Unknown 1 0.0% 670 670 670
38385 Addressable 1 0.0% 1350 1350 1350
22355 Ephemeral 1 0.0% 443 443 443
23333 Ephemeral 1 0.0% 571 571 571
22737 Ephemeral 1 0.0% 556 556 556
22291 Ephemeral 1 0.0% 443 443 443
30421 Addressable 1 0.0% 995 995 995
22860 Ephemeral 1 0.0% 553 553 553
2333 Unknown 1 0.0% 659 659 659
22219 Ephemeral 1 0.0% 443 443 443
22382 Ephemeral 1 0.0% 443 443 443
23032 Ephemeral 1 0.0% 430 430 430
7375 Unknown 1 0.0% 1822 1822 1822
22579 Ephemeral 1 0.0% 1670 1670 1670
10007 Unknown 1 0.0% 715 715 715
10086 Unknown 1 0.0% 715 715 715
22729 Ephemeral 1 0.0% 3030 3030 3030
22540 Ephemeral 1 0.0% 426 426 426
22671 Ephemeral 1 0.0% 3194 3194 3194
30618 Addressable 1 0.0% 460 460 460
23118 Ephemeral 1 0.0% 430 430 430
38421 Addressable 1 0.0% 1069 1069 1069
10377 Unknown 1 0.0% 664 664 664
11111 Unknown 1 0.0% 382 382 382
34550 Community def 1 0.0% 867 867 867
22307 Ephemeral 1 0.0% 443 443 443
1078 Unknown 1 0.0% 13969 13969 13969
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 16:24:15 2026
# Config: cache_all_test_primal.jsonc
# Duration: 3600 seconds
[2026-08-02 16:24:32] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 16:24:33] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:24:34] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:24:37] [RELAY] ? EOSE
[2026-08-02 16:28:28] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:28:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:29:27] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:29:28] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:29:32] [RELAY] ? EOSE
[2026-08-02 16:29:45] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:29:46] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:30:42] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:30:43] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:30:54] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:30:55] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:29] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:30] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:30] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:31] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:31] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:48] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:49] [RELAY] wss://relay.primal.net CONNECTED
@@ -0,0 +1,217 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 6m 16s
=== Global Summary ===
Total events: 5992
Total bytes: 8743262 (8.34 MB)
Avg event size: 1459 bytes
Max event: 61894 bytes (kind 3)
Unique pubkeys: 1142
Relays: 1
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 5992 0 0 0 0 3 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
30078 App data 495 8.3% 2024957 4091 54029
21059 Gift wrap (NIP-59) 386 6.4% 1099128 2847 5314
4515 Unknown 374 6.2% 781718 2090 14221
22591 Ephemeral 371 6.2% 286839 773 1228
20001 Ephemeral 271 4.5% 315643 1165 2029
22734 Ephemeral 264 4.4% 112728 427 427
30382 Addressable 255 4.3% 121375 476 483
30383 Addressable 237 4.0% 126938 536 547
1 Text note 212 3.5% 211399 997 2011
5 Deletion 183 3.1% 91915 502 664
22236 Ephemeral 146 2.4% 104252 714 810
0 Profile metadata 145 2.4% 103418 713 2012
7 Reaction 138 2.3% 79421 576 1036
22743 Ephemeral 134 2.2% 57352 428 428
1059 Gift wrap seal 132 2.2% 430222 3259 5285
29333 Ephemeral 130 2.2% 63960 492 492
22611 Ephemeral 117 2.0% 50147 429 429
10002 Relay list 114 1.9% 510211 4476 42449
23456 Ephemeral 72 1.2% 121616 1689 1874
22850 Ephemeral 71 1.2% 30530 430 430
22684 Ephemeral 70 1.2% 30170 431 431
22838 Ephemeral 63 1.1% 27027 429 429
23003 Ephemeral 63 1.1% 26964 428 428
22774 Ephemeral 62 1.0% 26536 428 428
22876 Ephemeral 61 1.0% 28880 473 3193
22583 Ephemeral 61 1.0% 26047 427 427
22613 Ephemeral 60 1.0% 43172 720 1133
4 Encrypted DM 58 1.0% 61778 1065 17018
25555 Ephemeral 56 0.9% 249772 4460 16895
25050 Ephemeral 51 0.9% 20451 401 401
11998 Unknown 48 0.8% 22272 464 465
22728 Ephemeral 48 0.8% 20592 429 429
31234 Addressable 33 0.6% 77859 2359 2612
13194 NWC info 32 0.5% 16448 514 514
20387 Ephemeral 32 0.5% 16448 514 514
24133 Ephemeral 32 0.5% 23680 740 2214
3 Follow list 31 0.5% 167289 5396 61894
4004 Unknown 26 0.4% 30394 1169 1169
22816 Ephemeral 26 0.4% 11154 429 429
20004 Ephemeral 26 0.4% 13878 534 549
30315 User status 26 0.4% 17657 679 872
443 Unknown 25 0.4% 43504 1740 22480
38501 Addressable 24 0.4% 17819 742 744
1985 Unknown 24 0.4% 11435 476 483
22882 Ephemeral 23 0.4% 20949 911 1455
5300 Unknown 22 0.4% 13434 611 701
31990 App handler info 22 0.4% 21777 990 6861
22790 Ephemeral 21 0.4% 8988 428 428
21050 Ephemeral 19 0.3% 17510 922 1874
10100 Unknown 19 0.3% 22108 1164 1589
20333 Ephemeral 18 0.3% 14340 797 798
30088 Addressable 16 0.3% 29798 1862 1871
6 Repost 16 0.3% 28131 1758 3369
30091 Addressable 16 0.3% 9842 615 629
6300 Unknown 15 0.3% 45643 3043 8157
22658 Ephemeral 15 0.3% 11909 794 1161
30311 Addressable 15 0.3% 16556 1104 1197
445 Encrypted group msg 15 0.3% 45070 3005 4130
10777 Unknown 14 0.2% 6840 489 489
9735 Zap receipt 14 0.2% 27231 1945 2777
4244 Unknown 12 0.2% 6768 564 564
22456 Ephemeral 12 0.2% 6808 567 790
38503 Addressable 11 0.2% 6070 552 552
10050 DM relays 11 0.2% 4882 444 592
30515 Addressable 10 0.2% 53040 5304 5304
30512 Addressable 10 0.2% 15210 1521 1596
30089 Chunked data 10 0.2% 164434 16443 44372
10000 Mute list 10 0.2% 16448 1645 4287
22809 Ephemeral 9 0.2% 3852 428 428
22486 Ephemeral 9 0.2% 3834 426 426
22376 Ephemeral 8 0.1% 3550 444 444
7368 Unknown 8 0.1% 8294 1037 1040
38500 Addressable 8 0.1% 5696 712 713
30380 Addressable 7 0.1% 3178 454 454
20000 Ephemeral 7 0.1% 2422 346 346
22104 Ephemeral 7 0.1% 3105 444 444
23025 Ephemeral 7 0.1% 3017 431 431
22896 Ephemeral 7 0.1% 3003 429 429
33334 Addressable 7 0.1% 5817 831 831
22835 Ephemeral 6 0.1% 2574 429 429
30443 Addressable 6 0.1% 7297 1216 1260
22633 Ephemeral 6 0.1% 6097 1016 1668
22392 Ephemeral 6 0.1% 2662 444 444
1984 Report 6 0.1% 3180 530 530
30064 Addressable 6 0.1% 4438 740 991
22715 Ephemeral 6 0.1% 2562 427 427
30280 Addressable 6 0.1% 3078 513 513
22559 Ephemeral 6 0.1% 2574 429 429
22813 Ephemeral 6 0.1% 2562 427 427
22635 Ephemeral 6 0.1% 2556 426 426
22746 Ephemeral 5 0.1% 2155 431 431
22364 Ephemeral 5 0.1% 2135 427 427
37195 Addressable 5 0.1% 3664 733 786
22571 Ephemeral 5 0.1% 4055 811 1127
23004 Ephemeral 5 0.1% 2145 429 429
22834 Ephemeral 5 0.1% 4050 810 1126
22636 Ephemeral 5 0.1% 3280 656 1164
22786 Ephemeral 4 0.1% 1720 430 430
25195 Ephemeral 4 0.1% 2712 678 678
4243 Unknown 4 0.1% 2256 564 564
22168 Ephemeral 4 0.1% 1774 444 444
22760 Ephemeral 4 0.1% 2239 560 695
22733 Ephemeral 4 0.1% 1696 424 424
22878 Ephemeral 3 0.1% 4059 1353 3203
22902 Ephemeral 3 0.1% 5023 1674 1675
22428 Ephemeral 3 0.1% 1331 444 444
25196 Ephemeral 3 0.1% 4938 1646 2214
22325 Ephemeral 3 0.1% 1331 444 444
4245 Unknown 3 0.1% 1332 444 444
22258 Ephemeral 3 0.1% 1331 444 444
22625 Ephemeral 3 0.1% 1284 428 428
22631 Ephemeral 3 0.1% 1281 427 427
22551 Ephemeral 3 0.1% 1816 605 695
22763 Ephemeral 3 0.1% 2988 996 1331
3927 Unknown 2 0.0% 1066 533 533
22186 Ephemeral 2 0.0% 887 444 444
30213 Addressable 2 0.0% 1264 632 632
22367 Ephemeral 2 0.0% 887 444 444
22783 Ephemeral 2 0.0% 1391 696 697
22616 Ephemeral 2 0.0% 858 429 429
22268 Ephemeral 2 0.0% 887 444 444
22279 Ephemeral 2 0.0% 887 444 444
22474 Ephemeral 2 0.0% 887 444 444
22407 Ephemeral 2 0.0% 887 444 444
22729 Ephemeral 2 0.0% 854 427 427
22234 Ephemeral 2 0.0% 887 444 444
10003 Bookmarks 2 0.0% 18782 9391 15076
30815 Addressable 2 0.0% 75068 37534 37534
1111 Comment 2 0.0% 1775 888 922
30087 Addressable 2 0.0% 13286 6643 7325
22256 Ephemeral 2 0.0% 886 443 443
22299 Ephemeral 2 0.0% 887 444 444
30090 Addressable 2 0.0% 2911 1456 1515
22260 Ephemeral 2 0.0% 887 444 444
7376 Unknown 2 0.0% 1340 670 670
22012 Ephemeral 2 0.0% 887 444 444
30618 Addressable 2 0.0% 920 460 460
1078 Unknown 2 0.0% 28397 14198 14219
22313 Ephemeral 2 0.0% 887 444 444
31339 Addressable 2 0.0% 2940 1470 1470
15750 Unknown 2 0.0% 1184 592 620
20 Picture 2 0.0% 1664 832 834
22326 Ephemeral 2 0.0% 887 444 444
22841 Ephemeral 1 0.0% 428 428 428
22738 Ephemeral 1 0.0% 1668 1668 1668
22295 Ephemeral 1 0.0% 443 443 443
22296 Ephemeral 1 0.0% 443 443 443
31510 Addressable 1 0.0% 979 979 979
22691 Ephemeral 1 0.0% 431 431 431
22340 Ephemeral 1 0.0% 443 443 443
30003 Addressable 1 0.0% 387 387 387
22197 Ephemeral 1 0.0% 443 443 443
22257 Ephemeral 1 0.0% 443 443 443
30079 Addressable 1 0.0% 875 875 875
22811 Ephemeral 1 0.0% 3211 3211 3211
38385 Addressable 1 0.0% 1350 1350 1350
22339 Ephemeral 1 0.0% 443 443 443
13196 Unknown 1 0.0% 656 656 656
20200 Ephemeral 1 0.0% 491 491 491
20201 Ephemeral 1 0.0% 491 491 491
22395 Ephemeral 1 0.0% 443 443 443
30023 Long-form article 1 0.0% 27062 27062 27062
7403 Unknown 1 0.0% 490 490 490
22618 Ephemeral 1 0.0% 1668 1668 1668
17 Unknown 1 0.0% 593 593 593
30000 Addressable 1 0.0% 823 823 823
22558 Ephemeral 1 0.0% 428 428 428
22143 Ephemeral 1 0.0% 443 443 443
22198 Ephemeral 1 0.0% 443 443 443
7375 Unknown 1 0.0% 2846 2846 2846
22506 Ephemeral 1 0.0% 426 426 426
22243 Ephemeral 1 0.0% 443 443 443
23086 Ephemeral 1 0.0% 431 431 431
22688 Ephemeral 1 0.0% 3201 3201 3201
22975 Ephemeral 1 0.0% 429 429 429
22270 Ephemeral 1 0.0% 443 443 443
22584 Ephemeral 1 0.0% 1675 1675 1675
22764 Ephemeral 1 0.0% 551 551 551
23071 Ephemeral 1 0.0% 1675 1675 1675
22817 Ephemeral 1 0.0% 559 559 559
22614 Ephemeral 1 0.0% 3200 3200 3200
22330 Ephemeral 1 0.0% 443 443 443
22229 Ephemeral 1 0.0% 443 443 443
24421 Ephemeral 1 0.0% 525 525 525
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 16:34:39 2026
# Config: cache_all_test_primal.jsonc
# Duration: 3600 seconds
[2026-08-02 16:34:56] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 16:34:57] [RELAY] ? EOSE
[2026-08-02 16:38:58] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:38:58] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:39:56] [RELAY] ? EOSE
[2026-08-02 16:40:16] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:40:17] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:40:17] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:40:18] [RELAY] wss://relay.primal.net CONNECTED
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,921 @@
{
"duration_seconds": 120,
"total_events": 4267,
"total_bytes": 6726611,
"max_event_bytes": 42385,
"max_event_kind": 10002,
"unique_pubkeys": 1860,
"relay_count": 3,
"relays": [{
"url": "wss://relay.primal.net",
"events": 1670,
"eose": 1,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 1792,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 805,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}],
"kinds": [{
"kind": 21059,
"count": 1199,
"total_bytes": 3547818,
"max_bytes": 5314
}, {
"kind": 5,
"count": 466,
"total_bytes": 204587,
"max_bytes": 1064
}, {
"kind": 30078,
"count": 222,
"total_bytes": 948782,
"max_bytes": 40337
}, {
"kind": 22668,
"count": 221,
"total_bytes": 94809,
"max_bytes": 429
}, {
"kind": 20001,
"count": 152,
"total_bytes": 106536,
"max_bytes": 1755
}, {
"kind": 4515,
"count": 152,
"total_bytes": 260732,
"max_bytes": 8759
}, {
"kind": 22734,
"count": 108,
"total_bytes": 46116,
"max_bytes": 427
}, {
"kind": 1,
"count": 105,
"total_bytes": 83026,
"max_bytes": 3482
}, {
"kind": 21050,
"count": 91,
"total_bytes": 89206,
"max_bytes": 2558
}, {
"kind": 24747,
"count": 85,
"total_bytes": 42775,
"max_bytes": 671
}, {
"kind": 22850,
"count": 77,
"total_bytes": 33110,
"max_bytes": 430
}, {
"kind": 22591,
"count": 66,
"total_bytes": 53607,
"max_bytes": 1222
}, {
"kind": 38501,
"count": 61,
"total_bytes": 45175,
"max_bytes": 744
}, {
"kind": 10002,
"count": 51,
"total_bytes": 63749,
"max_bytes": 42385
}, {
"kind": 0,
"count": 47,
"total_bytes": 33672,
"max_bytes": 1611
}, {
"kind": 37195,
"count": 44,
"total_bytes": 32855,
"max_bytes": 771
}, {
"kind": 24242,
"count": 37,
"total_bytes": 21423,
"max_bytes": 579
}, {
"kind": 25050,
"count": 36,
"total_bytes": 15972,
"max_bytes": 465
}, {
"kind": 7,
"count": 36,
"total_bytes": 18965,
"max_bytes": 646
}, {
"kind": 22613,
"count": 35,
"total_bytes": 26154,
"max_bytes": 1134
}, {
"kind": 22484,
"count": 32,
"total_bytes": 29125,
"max_bytes": 1163
}, {
"kind": 22749,
"count": 29,
"total_bytes": 12441,
"max_bytes": 429
}, {
"kind": 24133,
"count": 25,
"total_bytes": 37446,
"max_bytes": 7334
}, {
"kind": 22601,
"count": 25,
"total_bytes": 10700,
"max_bytes": 428
}, {
"kind": 22507,
"count": 24,
"total_bytes": 22582,
"max_bytes": 1165
}, {
"kind": 1059,
"count": 23,
"total_bytes": 41391,
"max_bytes": 2897
}, {
"kind": 22699,
"count": 23,
"total_bytes": 9844,
"max_bytes": 428
}, {
"kind": 22676,
"count": 23,
"total_bytes": 9821,
"max_bytes": 427
}, {
"kind": 22774,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 22795,
"count": 20,
"total_bytes": 8580,
"max_bytes": 429
}, {
"kind": 22655,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 23003,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 30382,
"count": 20,
"total_bytes": 9960,
"max_bytes": 498
}, {
"kind": 29333,
"count": 20,
"total_bytes": 9840,
"max_bytes": 492
}, {
"kind": 25555,
"count": 18,
"total_bytes": 62434,
"max_bytes": 17003
}, {
"kind": 11998,
"count": 18,
"total_bytes": 8322,
"max_bytes": 465
}, {
"kind": 31990,
"count": 18,
"total_bytes": 16845,
"max_bytes": 1465
}, {
"kind": 4,
"count": 18,
"total_bytes": 28064,
"max_bytes": 17018
}, {
"kind": 13194,
"count": 18,
"total_bytes": 9252,
"max_bytes": 514
}, {
"kind": 22689,
"count": 17,
"total_bytes": 7259,
"max_bytes": 427
}, {
"kind": 5300,
"count": 17,
"total_bytes": 10495,
"max_bytes": 701
}, {
"kind": 445,
"count": 17,
"total_bytes": 81892,
"max_bytes": 19620
}, {
"kind": 20000,
"count": 15,
"total_bytes": 7045,
"max_bytes": 546
}, {
"kind": 20387,
"count": 14,
"total_bytes": 7196,
"max_bytes": 514
}, {
"kind": 20004,
"count": 14,
"total_bytes": 7488,
"max_bytes": 549
}, {
"kind": 7368,
"count": 13,
"total_bytes": 13476,
"max_bytes": 1040
}, {
"kind": 22456,
"count": 13,
"total_bytes": 6110,
"max_bytes": 470
}, {
"kind": 9735,
"count": 13,
"total_bytes": 26072,
"max_bytes": 2331
}, {
"kind": 22551,
"count": 13,
"total_bytes": 11042,
"max_bytes": 1161
}, {
"kind": 22760,
"count": 13,
"total_bytes": 11014,
"max_bytes": 1161
}, {
"kind": 22658,
"count": 13,
"total_bytes": 11050,
"max_bytes": 1164
}, {
"kind": 10100,
"count": 11,
"total_bytes": 20684,
"max_bytes": 3825
}, {
"kind": 30315,
"count": 11,
"total_bytes": 8899,
"max_bytes": 1038
}, {
"kind": 20787,
"count": 11,
"total_bytes": 15458,
"max_bytes": 2474
}, {
"kind": 22236,
"count": 10,
"total_bytes": 7818,
"max_bytes": 814
}, {
"kind": 22832,
"count": 10,
"total_bytes": 4286,
"max_bytes": 429
}, {
"kind": 38502,
"count": 8,
"total_bytes": 3080,
"max_bytes": 385
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 22785,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 3,
"count": 8,
"total_bytes": 35850,
"max_bytes": 9845
}, {
"kind": 1985,
"count": 8,
"total_bytes": 3792,
"max_bytes": 474
}, {
"kind": 22584,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22816,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 22752,
"count": 8,
"total_bytes": 6212,
"max_bytes": 3199
}, {
"kind": 6,
"count": 7,
"total_bytes": 10112,
"max_bytes": 1872
}, {
"kind": 443,
"count": 7,
"total_bytes": 7780,
"max_bytes": 1116
}, {
"kind": 22651,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 1077,
"count": 6,
"total_bytes": 3642,
"max_bytes": 607
}, {
"kind": 26428,
"count": 6,
"total_bytes": 5254,
"max_bytes": 876
}, {
"kind": 22616,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22626,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 22914,
"count": 6,
"total_bytes": 2580,
"max_bytes": 430
}, {
"kind": 22614,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 22419,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22847,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 30088,
"count": 5,
"total_bytes": 9317,
"max_bytes": 1871
}, {
"kind": 10050,
"count": 5,
"total_bytes": 2250,
"max_bytes": 450
}, {
"kind": 30091,
"count": 5,
"total_bytes": 3085,
"max_bytes": 629
}, {
"kind": 30311,
"count": 5,
"total_bytes": 5153,
"max_bytes": 1197
}, {
"kind": 23080,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 38225,
"count": 4,
"total_bytes": 4077,
"max_bytes": 1036
}, {
"kind": 4244,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 26429,
"count": 4,
"total_bytes": 1964,
"max_bytes": 491
}, {
"kind": 3927,
"count": 4,
"total_bytes": 2132,
"max_bytes": 533
}, {
"kind": 22806,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 30000,
"count": 4,
"total_bytes": 9358,
"max_bytes": 2845
}, {
"kind": 21617,
"count": 4,
"total_bytes": 1980,
"max_bytes": 495
}, {
"kind": 20333,
"count": 4,
"total_bytes": 3188,
"max_bytes": 798
}, {
"kind": 22548,
"count": 4,
"total_bytes": 1720,
"max_bytes": 430
}, {
"kind": 38503,
"count": 4,
"total_bytes": 2212,
"max_bytes": 553
}, {
"kind": 30515,
"count": 4,
"total_bytes": 18496,
"max_bytes": 4624
}, {
"kind": 22740,
"count": 4,
"total_bytes": 1708,
"max_bytes": 427
}, {
"kind": 22226,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 10011,
"count": 3,
"total_bytes": 1375,
"max_bytes": 513
}, {
"kind": 30089,
"count": 3,
"total_bytes": 6944,
"max_bytes": 3412
}, {
"kind": 30023,
"count": 3,
"total_bytes": 19822,
"max_bytes": 7860
}, {
"kind": 21060,
"count": 3,
"total_bytes": 1710,
"max_bytes": 570
}, {
"kind": 22526,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 22511,
"count": 3,
"total_bytes": 2383,
"max_bytes": 1163
}, {
"kind": 22167,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 30213,
"count": 3,
"total_bytes": 1866,
"max_bytes": 632
}, {
"kind": 22748,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 31234,
"count": 3,
"total_bytes": 4248,
"max_bytes": 1416
}, {
"kind": 22838,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 38500,
"count": 2,
"total_bytes": 1424,
"max_bytes": 713
}, {
"kind": 38384,
"count": 2,
"total_bytes": 1486,
"max_bytes": 743
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2379,
"max_bytes": 1220
}, {
"kind": 20384,
"count": 2,
"total_bytes": 1278,
"max_bytes": 639
}, {
"kind": 34237,
"count": 2,
"total_bytes": 836,
"max_bytes": 418
}, {
"kind": 22280,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 23194,
"count": 2,
"total_bytes": 1020,
"max_bytes": 510
}, {
"kind": 22320,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30080,
"count": 2,
"total_bytes": 1971,
"max_bytes": 986
}, {
"kind": 22205,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22770,
"count": 2,
"total_bytes": 862,
"max_bytes": 431
}, {
"kind": 22305,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22356,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22611,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 22217,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22884,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 33334,
"count": 2,
"total_bytes": 1662,
"max_bytes": 831
}, {
"kind": 6300,
"count": 2,
"total_bytes": 8371,
"max_bytes": 5058
}, {
"kind": 22169,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22190,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30079,
"count": 2,
"total_bytes": 1750,
"max_bytes": 875
}, {
"kind": 39101,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 22345,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22310,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 1111,
"count": 2,
"total_bytes": 2557,
"max_bytes": 1564
}, {
"kind": 22235,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 38385,
"count": 2,
"total_bytes": 2552,
"max_bytes": 1319
}, {
"kind": 4747,
"count": 1,
"total_bytes": 721,
"max_bytes": 721
}, {
"kind": 22844,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22807,
"count": 1,
"total_bytes": 3205,
"max_bytes": 3205
}, {
"kind": 30066,
"count": 1,
"total_bytes": 675,
"max_bytes": 675
}, {
"kind": 4245,
"count": 1,
"total_bytes": 444,
"max_bytes": 444
}, {
"kind": 7376,
"count": 1,
"total_bytes": 521,
"max_bytes": 521
}, {
"kind": 22450,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22261,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 4243,
"count": 1,
"total_bytes": 564,
"max_bytes": 564
}, {
"kind": 22242,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 10063,
"count": 1,
"total_bytes": 415,
"max_bytes": 415
}, {
"kind": 22306,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22174,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22831,
"count": 1,
"total_bytes": 3182,
"max_bytes": 3182
}, {
"kind": 22227,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 7375,
"count": 1,
"total_bytes": 8629,
"max_bytes": 8629
}, {
"kind": 22685,
"count": 1,
"total_bytes": 553,
"max_bytes": 553
}, {
"kind": 22299,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22193,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22975,
"count": 1,
"total_bytes": 3195,
"max_bytes": 3195
}, {
"kind": 30380,
"count": 1,
"total_bytes": 454,
"max_bytes": 454
}, {
"kind": 20,
"count": 1,
"total_bytes": 860,
"max_bytes": 860
}, {
"kind": 10030,
"count": 1,
"total_bytes": 405,
"max_bytes": 405
}, {
"kind": 30087,
"count": 1,
"total_bytes": 7325,
"max_bytes": 7325
}, {
"kind": 30090,
"count": 1,
"total_bytes": 1515,
"max_bytes": 1515
}, {
"kind": 22220,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 39019,
"count": 1,
"total_bytes": 961,
"max_bytes": 961
}, {
"kind": 39015,
"count": 1,
"total_bytes": 696,
"max_bytes": 696
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 31991,
"count": 1,
"total_bytes": 461,
"max_bytes": 461
}, {
"kind": 1080,
"count": 1,
"total_bytes": 949,
"max_bytes": 949
}, {
"kind": 3434,
"count": 1,
"total_bytes": 705,
"max_bytes": 705
}, {
"kind": 33333,
"count": 1,
"total_bytes": 2346,
"max_bytes": 2346
}, {
"kind": 22735,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 22415,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 21966,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 11316,
"count": 1,
"total_bytes": 725,
"max_bytes": 725
}, {
"kind": 11317,
"count": 1,
"total_bytes": 12051,
"max_bytes": 12051
}, {
"kind": 25195,
"count": 1,
"total_bytes": 678,
"max_bytes": 678
}, {
"kind": 25196,
"count": 1,
"total_bytes": 1362,
"max_bytes": 1362
}, {
"kind": 22402,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 31510,
"count": 1,
"total_bytes": 979,
"max_bytes": 979
}, {
"kind": 1078,
"count": 1,
"total_bytes": 14200,
"max_bytes": 14200
}, {
"kind": 38383,
"count": 1,
"total_bytes": 685,
"max_bytes": 685
}]
}
@@ -0,0 +1,916 @@
{
"duration_seconds": 120,
"total_events": 5012,
"total_bytes": 8091410,
"max_event_bytes": 52672,
"max_event_kind": 3,
"unique_pubkeys": 2034,
"relay_count": 3,
"relays": [{
"url": "wss://relay.primal.net",
"events": 1599,
"eose": 1,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 2389,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 1024,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}],
"kinds": [{
"kind": 21059,
"count": 1361,
"total_bytes": 4189338,
"max_bytes": 5314
}, {
"kind": 5,
"count": 579,
"total_bytes": 254363,
"max_bytes": 624
}, {
"kind": 22668,
"count": 265,
"total_bytes": 113685,
"max_bytes": 429
}, {
"kind": 30078,
"count": 190,
"total_bytes": 702178,
"max_bytes": 14343
}, {
"kind": 20001,
"count": 181,
"total_bytes": 108257,
"max_bytes": 1489
}, {
"kind": 22639,
"count": 176,
"total_bytes": 136058,
"max_bytes": 1228
}, {
"kind": 4515,
"count": 168,
"total_bytes": 319242,
"max_bytes": 14220
}, {
"kind": 24133,
"count": 152,
"total_bytes": 464764,
"max_bytes": 14162
}, {
"kind": 22734,
"count": 135,
"total_bytes": 57645,
"max_bytes": 427
}, {
"kind": 31991,
"count": 116,
"total_bytes": 53476,
"max_bytes": 461
}, {
"kind": 21050,
"count": 84,
"total_bytes": 81900,
"max_bytes": 2558
}, {
"kind": 1,
"count": 80,
"total_bytes": 58412,
"max_bytes": 1443
}, {
"kind": 22795,
"count": 76,
"total_bytes": 104000,
"max_bytes": 1848
}, {
"kind": 22850,
"count": 75,
"total_bytes": 33353,
"max_bytes": 1169
}, {
"kind": 38501,
"count": 69,
"total_bytes": 51105,
"max_bytes": 744
}, {
"kind": 22842,
"count": 55,
"total_bytes": 92475,
"max_bytes": 1849
}, {
"kind": 10002,
"count": 48,
"total_bytes": 19784,
"max_bytes": 537
}, {
"kind": 0,
"count": 47,
"total_bytes": 34961,
"max_bytes": 1356
}, {
"kind": 37195,
"count": 38,
"total_bytes": 28214,
"max_bytes": 771
}, {
"kind": 7,
"count": 38,
"total_bytes": 21600,
"max_bytes": 779
}, {
"kind": 25050,
"count": 36,
"total_bytes": 15972,
"max_bytes": 465
}, {
"kind": 24242,
"count": 34,
"total_bytes": 19686,
"max_bytes": 579
}, {
"kind": 1059,
"count": 33,
"total_bytes": 58147,
"max_bytes": 3298
}, {
"kind": 22484,
"count": 32,
"total_bytes": 29156,
"max_bytes": 1164
}, {
"kind": 22507,
"count": 28,
"total_bytes": 24308,
"max_bytes": 1165
}, {
"kind": 22613,
"count": 25,
"total_bytes": 17238,
"max_bytes": 691
}, {
"kind": 29333,
"count": 25,
"total_bytes": 12300,
"max_bytes": 492
}, {
"kind": 22601,
"count": 24,
"total_bytes": 10272,
"max_bytes": 428
}, {
"kind": 5300,
"count": 23,
"total_bytes": 14096,
"max_bytes": 701
}, {
"kind": 22751,
"count": 23,
"total_bytes": 9798,
"max_bytes": 426
}, {
"kind": 22699,
"count": 22,
"total_bytes": 9416,
"max_bytes": 428
}, {
"kind": 11998,
"count": 22,
"total_bytes": 10182,
"max_bytes": 465
}, {
"kind": 22676,
"count": 22,
"total_bytes": 9394,
"max_bytes": 427
}, {
"kind": 13194,
"count": 21,
"total_bytes": 10617,
"max_bytes": 514
}, {
"kind": 22774,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 23003,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 22655,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22543,
"count": 19,
"total_bytes": 34993,
"max_bytes": 1848
}, {
"kind": 22455,
"count": 19,
"total_bytes": 34943,
"max_bytes": 1844
}, {
"kind": 25555,
"count": 19,
"total_bytes": 94673,
"max_bytes": 17111
}, {
"kind": 30443,
"count": 19,
"total_bytes": 22379,
"max_bytes": 1260
}, {
"kind": 22777,
"count": 19,
"total_bytes": 16685,
"max_bytes": 1651
}, {
"kind": 22891,
"count": 18,
"total_bytes": 33189,
"max_bytes": 1848
}, {
"kind": 31990,
"count": 17,
"total_bytes": 15825,
"max_bytes": 1465
}, {
"kind": 20000,
"count": 16,
"total_bytes": 7206,
"max_bytes": 522
}, {
"kind": 32123,
"count": 14,
"total_bytes": 10528,
"max_bytes": 900
}, {
"kind": 22456,
"count": 13,
"total_bytes": 6110,
"max_bytes": 470
}, {
"kind": 20004,
"count": 13,
"total_bytes": 6741,
"max_bytes": 549
}, {
"kind": 22591,
"count": 13,
"total_bytes": 6348,
"max_bytes": 697
}, {
"kind": 20787,
"count": 12,
"total_bytes": 16864,
"max_bytes": 2478
}, {
"kind": 4,
"count": 12,
"total_bytes": 7972,
"max_bytes": 786
}, {
"kind": 20387,
"count": 12,
"total_bytes": 6168,
"max_bytes": 514
}, {
"kind": 38502,
"count": 12,
"total_bytes": 4620,
"max_bytes": 385
}, {
"kind": 10100,
"count": 11,
"total_bytes": 20938,
"max_bytes": 3825
}, {
"kind": 20050,
"count": 11,
"total_bytes": 9711,
"max_bytes": 891
}, {
"kind": 3,
"count": 10,
"total_bytes": 72137,
"max_bytes": 52672
}, {
"kind": 9735,
"count": 10,
"total_bytes": 20833,
"max_bytes": 2143
}, {
"kind": 445,
"count": 10,
"total_bytes": 17866,
"max_bytes": 4126
}, {
"kind": 22626,
"count": 10,
"total_bytes": 4280,
"max_bytes": 428
}, {
"kind": 7368,
"count": 9,
"total_bytes": 9343,
"max_bytes": 1041
}, {
"kind": 22736,
"count": 9,
"total_bytes": 3816,
"max_bytes": 424
}, {
"kind": 1985,
"count": 8,
"total_bytes": 3810,
"max_bytes": 483
}, {
"kind": 30091,
"count": 8,
"total_bytes": 4927,
"max_bytes": 629
}, {
"kind": 30315,
"count": 8,
"total_bytes": 4522,
"max_bytes": 872
}, {
"kind": 25055,
"count": 8,
"total_bytes": 10605,
"max_bytes": 1764
}, {
"kind": 22791,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22816,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 30088,
"count": 8,
"total_bytes": 14929,
"max_bytes": 1871
}, {
"kind": 31234,
"count": 8,
"total_bytes": 14048,
"max_bytes": 1756
}, {
"kind": 22813,
"count": 7,
"total_bytes": 2996,
"max_bytes": 428
}, {
"kind": 22957,
"count": 7,
"total_bytes": 3010,
"max_bytes": 430
}, {
"kind": 30089,
"count": 7,
"total_bytes": 45972,
"max_bytes": 27988
}, {
"kind": 32678,
"count": 7,
"total_bytes": 5704,
"max_bytes": 900
}, {
"kind": 22832,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22897,
"count": 6,
"total_bytes": 2586,
"max_bytes": 431
}, {
"kind": 22549,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22702,
"count": 6,
"total_bytes": 4753,
"max_bytes": 2618
}, {
"kind": 20333,
"count": 6,
"total_bytes": 4780,
"max_bytes": 798
}, {
"kind": 30023,
"count": 6,
"total_bytes": 21101,
"max_bytes": 5056
}, {
"kind": 22568,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 38385,
"count": 5,
"total_bytes": 6849,
"max_bytes": 1496
}, {
"kind": 4244,
"count": 5,
"total_bytes": 2820,
"max_bytes": 564
}, {
"kind": 22717,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 21617,
"count": 4,
"total_bytes": 1980,
"max_bytes": 495
}, {
"kind": 38225,
"count": 4,
"total_bytes": 4061,
"max_bytes": 1039
}, {
"kind": 6,
"count": 4,
"total_bytes": 8823,
"max_bytes": 3647
}, {
"kind": 22333,
"count": 4,
"total_bytes": 1773,
"max_bytes": 444
}, {
"kind": 22962,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 33334,
"count": 3,
"total_bytes": 2229,
"max_bytes": 831
}, {
"kind": 1002,
"count": 3,
"total_bytes": 2793,
"max_bytes": 979
}, {
"kind": 22244,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22907,
"count": 3,
"total_bytes": 1287,
"max_bytes": 429
}, {
"kind": 10011,
"count": 3,
"total_bytes": 1229,
"max_bytes": 431
}, {
"kind": 22258,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22245,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 30618,
"count": 3,
"total_bytes": 1755,
"max_bytes": 733
}, {
"kind": 22296,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 38503,
"count": 3,
"total_bytes": 1659,
"max_bytes": 553
}, {
"kind": 22338,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22195,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22342,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 10050,
"count": 3,
"total_bytes": 1350,
"max_bytes": 450
}, {
"kind": 30311,
"count": 3,
"total_bytes": 3414,
"max_bytes": 1197
}, {
"kind": 32680,
"count": 2,
"total_bytes": 1376,
"max_bytes": 688
}, {
"kind": 30380,
"count": 2,
"total_bytes": 908,
"max_bytes": 454
}, {
"kind": 22611,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 4243,
"count": 2,
"total_bytes": 1128,
"max_bytes": 564
}, {
"kind": 22526,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 22320,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 4245,
"count": 2,
"total_bytes": 888,
"max_bytes": 444
}, {
"kind": 1984,
"count": 2,
"total_bytes": 873,
"max_bytes": 446
}, {
"kind": 1111,
"count": 2,
"total_bytes": 3115,
"max_bytes": 1558
}, {
"kind": 22311,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22662,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22089,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22221,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22157,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 38384,
"count": 2,
"total_bytes": 1486,
"max_bytes": 743
}, {
"kind": 20384,
"count": 2,
"total_bytes": 1278,
"max_bytes": 639
}, {
"kind": 30080,
"count": 2,
"total_bytes": 1679,
"max_bytes": 985
}, {
"kind": 39101,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 6300,
"count": 2,
"total_bytes": 11904,
"max_bytes": 6898
}, {
"kind": 10003,
"count": 2,
"total_bytes": 14466,
"max_bytes": 7233
}, {
"kind": 30515,
"count": 2,
"total_bytes": 9248,
"max_bytes": 4624
}, {
"kind": 22829,
"count": 2,
"total_bytes": 6387,
"max_bytes": 3194
}, {
"kind": 22106,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 31927,
"count": 2,
"total_bytes": 3000,
"max_bytes": 1500
}, {
"kind": 31510,
"count": 2,
"total_bytes": 1959,
"max_bytes": 980
}, {
"kind": 3927,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 1040,
"count": 2,
"total_bytes": 2096,
"max_bytes": 1048
}, {
"kind": 31000,
"count": 2,
"total_bytes": 1204,
"max_bytes": 616
}, {
"kind": 22201,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22269,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22818,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 22364,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 25195,
"count": 1,
"total_bytes": 678,
"max_bytes": 678
}, {
"kind": 25196,
"count": 1,
"total_bytes": 1362,
"max_bytes": 1362
}, {
"kind": 22283,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 10030,
"count": 1,
"total_bytes": 405,
"max_bytes": 405
}, {
"kind": 22706,
"count": 1,
"total_bytes": 3192,
"max_bytes": 3192
}, {
"kind": 22236,
"count": 1,
"total_bytes": 737,
"max_bytes": 737
}, {
"kind": 31339,
"count": 1,
"total_bytes": 1470,
"max_bytes": 1470
}, {
"kind": 22651,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 38500,
"count": 1,
"total_bytes": 713,
"max_bytes": 713
}, {
"kind": 10000,
"count": 1,
"total_bytes": 1311,
"max_bytes": 1311
}, {
"kind": 31926,
"count": 1,
"total_bytes": 1038,
"max_bytes": 1038
}, {
"kind": 22757,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 39015,
"count": 1,
"total_bytes": 696,
"max_bytes": 696
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 22234,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30815,
"count": 1,
"total_bytes": 37534,
"max_bytes": 37534
}, {
"kind": 1078,
"count": 1,
"total_bytes": 13939,
"max_bytes": 13939
}, {
"kind": 39050,
"count": 1,
"total_bytes": 1843,
"max_bytes": 1843
}, {
"kind": 22281,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30166,
"count": 1,
"total_bytes": 808,
"max_bytes": 808
}, {
"kind": 22772,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22356,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30554,
"count": 1,
"total_bytes": 609,
"max_bytes": 609
}, {
"kind": 38422,
"count": 1,
"total_bytes": 20025,
"max_bytes": 20025
}, {
"kind": 38000,
"count": 1,
"total_bytes": 562,
"max_bytes": 562
}, {
"kind": 22069,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 1050,
"count": 1,
"total_bytes": 437,
"max_bytes": 437
}, {
"kind": 33051,
"count": 1,
"total_bytes": 522,
"max_bytes": 522
}, {
"kind": 39036,
"count": 1,
"total_bytes": 1138,
"max_bytes": 1138
}, {
"kind": 23194,
"count": 1,
"total_bytes": 510,
"max_bytes": 510
}, {
"kind": 30081,
"count": 1,
"total_bytes": 1370,
"max_bytes": 1370
}, {
"kind": 22299,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22556,
"count": 1,
"total_bytes": 427,
"max_bytes": 427
}, {
"kind": 22302,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 39019,
"count": 1,
"total_bytes": 961,
"max_bytes": 961
}, {
"kind": 30000,
"count": 1,
"total_bytes": 823,
"max_bytes": 823
}, {
"kind": 38383,
"count": 1,
"total_bytes": 657,
"max_bytes": 657
}, {
"kind": 30087,
"count": 1,
"total_bytes": 7325,
"max_bytes": 7325
}, {
"kind": 30090,
"count": 1,
"total_bytes": 1515,
"max_bytes": 1515
}]
}
@@ -0,0 +1,281 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 27,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.wine",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.oxtr.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://spatia-arcana.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://eden.nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.band",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://theforest.nostr1.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://wot.utxo.one",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://premium.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.mom",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.mostr.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://offchain.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://devrelay.azzamo.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.noderunners.network",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr-pub.semisol.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrcheck.me",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.info",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.momostr.pink",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://bitsat.molonlabe.holdings",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,161 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 15,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.oxtr.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://pyramid.fiatjaf.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.mostr.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.wine",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://theforest.nostr1.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.band",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.info",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,121 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 11,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.bitcoiner.social",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://pyramid.fiatjaf.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://eden.nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,834 @@
{
"duration_seconds": 114,
"total_events": 4719,
"total_bytes": 6608416,
"unique_pubkeys": 1333,
"relay_count": 15,
"relays": [{
"url": "wss://laantungir.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://nos.lol",
"events": 804,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 3,
"connected": true
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 83,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.mom",
"events": 175,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.oxtr.dev",
"events": 108,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://offchain.pub",
"events": 627,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://premium.primal.net",
"events": 1,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://relay.ditto.pub",
"events": 721,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.divine.video",
"events": 3,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.momostr.pink",
"events": 86,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.mostr.pub",
"events": 836,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.nostrplebs.com",
"events": 4,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.primal.net",
"events": 1239,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://theforest.nostr1.com",
"events": 32,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}],
"kinds": [{
"kind": 21059,
"count": 675,
"total_bytes": 2192938,
"max_bytes": 5314
}, {
"kind": 30382,
"count": 563,
"total_bytes": 255591,
"max_bytes": 498
}, {
"kind": 5,
"count": 341,
"total_bytes": 153667,
"max_bytes": 1404
}, {
"kind": 22639,
"count": 231,
"total_bytes": 178011,
"max_bytes": 1230
}, {
"kind": 22551,
"count": 182,
"total_bytes": 78260,
"max_bytes": 430
}, {
"kind": 20001,
"count": 143,
"total_bytes": 70427,
"max_bytes": 1206
}, {
"kind": 30078,
"count": 141,
"total_bytes": 1361122,
"max_bytes": 44169
}, {
"kind": 0,
"count": 135,
"total_bytes": 119793,
"max_bytes": 3483
}, {
"kind": 22734,
"count": 122,
"total_bytes": 52094,
"max_bytes": 427
}, {
"kind": 24747,
"count": 101,
"total_bytes": 59352,
"max_bytes": 594
}, {
"kind": 1,
"count": 90,
"total_bytes": 92140,
"max_bytes": 4709
}, {
"kind": 22668,
"count": 88,
"total_bytes": 37752,
"max_bytes": 429
}, {
"kind": 22850,
"count": 83,
"total_bytes": 35690,
"max_bytes": 430
}, {
"kind": 22870,
"count": 76,
"total_bytes": 77061,
"max_bytes": 2202
}, {
"kind": 22951,
"count": 75,
"total_bytes": 77282,
"max_bytes": 2204
}, {
"kind": 10002,
"count": 75,
"total_bytes": 202196,
"max_bytes": 42385
}, {
"kind": 22912,
"count": 74,
"total_bytes": 31672,
"max_bytes": 428
}, {
"kind": 38501,
"count": 65,
"total_bytes": 48152,
"max_bytes": 744
}, {
"kind": 24133,
"count": 63,
"total_bytes": 72262,
"max_bytes": 2898
}, {
"kind": 6,
"count": 50,
"total_bytes": 40985,
"max_bytes": 3346
}, {
"kind": 37195,
"count": 48,
"total_bytes": 36225,
"max_bytes": 771
}, {
"kind": 21050,
"count": 48,
"total_bytes": 46800,
"max_bytes": 2558
}, {
"kind": 4515,
"count": 46,
"total_bytes": 81638,
"max_bytes": 6028
}, {
"kind": 25555,
"count": 45,
"total_bytes": 209551,
"max_bytes": 16603
}, {
"kind": 7,
"count": 40,
"total_bytes": 22486,
"max_bytes": 805
}, {
"kind": 22917,
"count": 39,
"total_bytes": 35435,
"max_bytes": 1166
}, {
"kind": 22699,
"count": 39,
"total_bytes": 26723,
"max_bytes": 1166
}, {
"kind": 22690,
"count": 39,
"total_bytes": 16770,
"max_bytes": 430
}, {
"kind": 22918,
"count": 38,
"total_bytes": 36470,
"max_bytes": 1507
}, {
"kind": 22832,
"count": 37,
"total_bytes": 16128,
"max_bytes": 446
}, {
"kind": 25050,
"count": 35,
"total_bytes": 15507,
"max_bytes": 465
}, {
"kind": 30383,
"count": 35,
"total_bytes": 18766,
"max_bytes": 541
}, {
"kind": 22713,
"count": 34,
"total_bytes": 14570,
"max_bytes": 430
}, {
"kind": 22698,
"count": 34,
"total_bytes": 31371,
"max_bytes": 1674
}, {
"kind": 22601,
"count": 28,
"total_bytes": 11984,
"max_bytes": 428
}, {
"kind": 22655,
"count": 28,
"total_bytes": 11984,
"max_bytes": 428
}, {
"kind": 1059,
"count": 23,
"total_bytes": 46912,
"max_bytes": 5285
}, {
"kind": 22751,
"count": 21,
"total_bytes": 8946,
"max_bytes": 426
}, {
"kind": 22676,
"count": 21,
"total_bytes": 8967,
"max_bytes": 427
}, {
"kind": 29333,
"count": 20,
"total_bytes": 9840,
"max_bytes": 492
}, {
"kind": 5300,
"count": 20,
"total_bytes": 11959,
"max_bytes": 630
}, {
"kind": 22743,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22603,
"count": 19,
"total_bytes": 8094,
"max_bytes": 426
}, {
"kind": 22693,
"count": 19,
"total_bytes": 8113,
"max_bytes": 427
}, {
"kind": 22774,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22808,
"count": 19,
"total_bytes": 8151,
"max_bytes": 429
}, {
"kind": 22575,
"count": 19,
"total_bytes": 8075,
"max_bytes": 425
}, {
"kind": 23003,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22949,
"count": 19,
"total_bytes": 8151,
"max_bytes": 429
}, {
"kind": 23334,
"count": 19,
"total_bytes": 8189,
"max_bytes": 431
}, {
"kind": 24242,
"count": 17,
"total_bytes": 9843,
"max_bytes": 579
}, {
"kind": 22816,
"count": 16,
"total_bytes": 6864,
"max_bytes": 429
}, {
"kind": 20787,
"count": 16,
"total_bytes": 25100,
"max_bytes": 2478
}, {
"kind": 13194,
"count": 15,
"total_bytes": 7574,
"max_bytes": 514
}, {
"kind": 38502,
"count": 14,
"total_bytes": 5390,
"max_bytes": 385
}, {
"kind": 22236,
"count": 14,
"total_bytes": 11214,
"max_bytes": 814
}, {
"kind": 22613,
"count": 13,
"total_bytes": 8977,
"max_bytes": 694
}, {
"kind": 30088,
"count": 12,
"total_bytes": 22340,
"max_bytes": 1871
}, {
"kind": 22484,
"count": 12,
"total_bytes": 10948,
"max_bytes": 1165
}, {
"kind": 30091,
"count": 12,
"total_bytes": 7385,
"max_bytes": 629
}, {
"kind": 20004,
"count": 11,
"total_bytes": 5643,
"max_bytes": 549
}, {
"kind": 31990,
"count": 11,
"total_bytes": 8711,
"max_bytes": 1465
}, {
"kind": 445,
"count": 10,
"total_bytes": 17972,
"max_bytes": 4126
}, {
"kind": 22795,
"count": 9,
"total_bytes": 3861,
"max_bytes": 429
}, {
"kind": 22507,
"count": 9,
"total_bytes": 8480,
"max_bytes": 1165
}, {
"kind": 22922,
"count": 9,
"total_bytes": 3870,
"max_bytes": 430
}, {
"kind": 24421,
"count": 9,
"total_bytes": 4644,
"max_bytes": 516
}, {
"kind": 22830,
"count": 9,
"total_bytes": 7317,
"max_bytes": 1168
}, {
"kind": 20000,
"count": 9,
"total_bytes": 3854,
"max_bytes": 484
}, {
"kind": 22955,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22793,
"count": 9,
"total_bytes": 3843,
"max_bytes": 427
}, {
"kind": 30315,
"count": 8,
"total_bytes": 4604,
"max_bytes": 926
}, {
"kind": 20387,
"count": 8,
"total_bytes": 4112,
"max_bytes": 514
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8309,
"max_bytes": 1041
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 443,
"count": 8,
"total_bytes": 8928,
"max_bytes": 1116
}, {
"kind": 11998,
"count": 7,
"total_bytes": 3255,
"max_bytes": 465
}, {
"kind": 10100,
"count": 7,
"total_bytes": 11851,
"max_bytes": 3825
}, {
"kind": 22456,
"count": 7,
"total_bytes": 3290,
"max_bytes": 470
}, {
"kind": 30089,
"count": 7,
"total_bytes": 157322,
"max_bytes": 44372
}, {
"kind": 22737,
"count": 7,
"total_bytes": 4669,
"max_bytes": 707
}, {
"kind": 23337,
"count": 7,
"total_bytes": 3557,
"max_bytes": 509
}, {
"kind": 30515,
"count": 7,
"total_bytes": 18032,
"max_bytes": 2576
}, {
"kind": 30311,
"count": 6,
"total_bytes": 5818,
"max_bytes": 1197
}, {
"kind": 1985,
"count": 6,
"total_bytes": 2860,
"max_bytes": 482
}, {
"kind": 22859,
"count": 5,
"total_bytes": 8606,
"max_bytes": 2195
}, {
"kind": 4,
"count": 5,
"total_bytes": 3222,
"max_bytes": 722
}, {
"kind": 10011,
"count": 5,
"total_bytes": 2027,
"max_bytes": 431
}, {
"kind": 22764,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 22777,
"count": 4,
"total_bytes": 2790,
"max_bytes": 698
}, {
"kind": 4244,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 30023,
"count": 4,
"total_bytes": 22789,
"max_bytes": 6817
}, {
"kind": 9735,
"count": 4,
"total_bytes": 7033,
"max_bytes": 1805
}, {
"kind": 20333,
"count": 4,
"total_bytes": 3188,
"max_bytes": 798
}, {
"kind": 3,
"count": 3,
"total_bytes": 26232,
"max_bytes": 14166
}, {
"kind": 38503,
"count": 3,
"total_bytes": 1655,
"max_bytes": 552
}, {
"kind": 10050,
"count": 3,
"total_bytes": 1411,
"max_bytes": 511
}, {
"kind": 21617,
"count": 3,
"total_bytes": 1485,
"max_bytes": 495
}, {
"kind": 1984,
"count": 3,
"total_bytes": 1986,
"max_bytes": 662
}, {
"kind": 3927,
"count": 3,
"total_bytes": 1599,
"max_bytes": 533
}, {
"kind": 3434,
"count": 3,
"total_bytes": 1907,
"max_bytes": 665
}, {
"kind": 22514,
"count": 2,
"total_bytes": 856,
"max_bytes": 428
}, {
"kind": 33334,
"count": 2,
"total_bytes": 1662,
"max_bytes": 831
}, {
"kind": 38500,
"count": 2,
"total_bytes": 1424,
"max_bytes": 713
}, {
"kind": 22526,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 30380,
"count": 2,
"total_bytes": 908,
"max_bytes": 454
}, {
"kind": 23194,
"count": 2,
"total_bytes": 1020,
"max_bytes": 510
}, {
"kind": 23042,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 38385,
"count": 2,
"total_bytes": 2692,
"max_bytes": 1478
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2369,
"max_bytes": 1219
}, {
"kind": 22747,
"count": 2,
"total_bytes": 2064,
"max_bytes": 1288
}, {
"kind": 4245,
"count": 2,
"total_bytes": 888,
"max_bytes": 444
}, {
"kind": 1111,
"count": 2,
"total_bytes": 1936,
"max_bytes": 981
}, {
"kind": 38383,
"count": 2,
"total_bytes": 1439,
"max_bytes": 720
}, {
"kind": 4243,
"count": 2,
"total_bytes": 1128,
"max_bytes": 564
}, {
"kind": 38225,
"count": 1,
"total_bytes": 978,
"max_bytes": 978
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 31510,
"count": 1,
"total_bytes": 981,
"max_bytes": 981
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 1077,
"count": 1,
"total_bytes": 607,
"max_bytes": 607
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 1078,
"count": 1,
"total_bytes": 14173,
"max_bytes": 14173
}, {
"kind": 6300,
"count": 1,
"total_bytes": 3698,
"max_bytes": 3698
}, {
"kind": 11316,
"count": 1,
"total_bytes": 791,
"max_bytes": 791
}, {
"kind": 11317,
"count": 1,
"total_bytes": 2873,
"max_bytes": 2873
}, {
"kind": 17,
"count": 1,
"total_bytes": 610,
"max_bytes": 610
}, {
"kind": 23059,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22806,
"count": 1,
"total_bytes": 1520,
"max_bytes": 1520
}, {
"kind": 22120,
"count": 1,
"total_bytes": 1127,
"max_bytes": 1127
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 24913,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 12222,
"count": 1,
"total_bytes": 579,
"max_bytes": 579
}, {
"kind": 1040,
"count": 1,
"total_bytes": 1048,
"max_bytes": 1048
}, {
"kind": 22119,
"count": 1,
"total_bytes": 845,
"max_bytes": 845
}, {
"kind": 10312,
"count": 1,
"total_bytes": 472,
"max_bytes": 472
}, {
"kind": 3079,
"count": 1,
"total_bytes": 909,
"max_bytes": 909
}, {
"kind": 30000,
"count": 1,
"total_bytes": 594,
"max_bytes": 594
}]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,704 @@
{
"duration_seconds": 32,
"total_events": 1485,
"total_bytes": 1812166,
"unique_pubkeys": 511,
"relay_count": 15,
"relays": [{
"url": "wss://laantungir.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://nos.lol",
"events": 247,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 37,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.mom",
"events": 38,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.oxtr.dev",
"events": 36,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://offchain.pub",
"events": 113,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://premium.primal.net",
"events": 1,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.damus.io",
"events": 194,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.ditto.pub",
"events": 277,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 1,
"connected": true
}, {
"url": "wss://relay.divine.video",
"events": 2,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.momostr.pink",
"events": 27,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.mostr.pub",
"events": 148,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.primal.net",
"events": 360,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://theforest.nostr1.com",
"events": 5,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}],
"kinds": [{
"kind": 21059,
"count": 184,
"total_bytes": 623532,
"max_bytes": 5314
}, {
"kind": 22591,
"count": 139,
"total_bytes": 107560,
"max_bytes": 1223
}, {
"kind": 30382,
"count": 119,
"total_bytes": 53663,
"max_bytes": 458
}, {
"kind": 30383,
"count": 119,
"total_bytes": 53605,
"max_bytes": 536
}, {
"kind": 5,
"count": 91,
"total_bytes": 39950,
"max_bytes": 624
}, {
"kind": 30078,
"count": 54,
"total_bytes": 179695,
"max_bytes": 14343
}, {
"kind": 20001,
"count": 49,
"total_bytes": 35635,
"max_bytes": 1871
}, {
"kind": 0,
"count": 48,
"total_bytes": 39906,
"max_bytes": 1468
}, {
"kind": 22668,
"count": 48,
"total_bytes": 20592,
"max_bytes": 429
}, {
"kind": 1,
"count": 45,
"total_bytes": 38547,
"max_bytes": 2413
}, {
"kind": 22734,
"count": 26,
"total_bytes": 11102,
"max_bytes": 427
}, {
"kind": 4515,
"count": 26,
"total_bytes": 38218,
"max_bytes": 2956
}, {
"kind": 10002,
"count": 25,
"total_bytes": 11707,
"max_bytes": 565
}, {
"kind": 22850,
"count": 24,
"total_bytes": 10320,
"max_bytes": 430
}, {
"kind": 7,
"count": 24,
"total_bytes": 13528,
"max_bytes": 780
}, {
"kind": 22551,
"count": 24,
"total_bytes": 10320,
"max_bytes": 430
}, {
"kind": 31991,
"count": 20,
"total_bytes": 9220,
"max_bytes": 461
}, {
"kind": 38501,
"count": 18,
"total_bytes": 13341,
"max_bytes": 744
}, {
"kind": 22663,
"count": 17,
"total_bytes": 7276,
"max_bytes": 428
}, {
"kind": 25555,
"count": 15,
"total_bytes": 90493,
"max_bytes": 16887
}, {
"kind": 22912,
"count": 13,
"total_bytes": 5564,
"max_bytes": 428
}, {
"kind": 1059,
"count": 13,
"total_bytes": 23775,
"max_bytes": 1883
}, {
"kind": 22832,
"count": 12,
"total_bytes": 5233,
"max_bytes": 446
}, {
"kind": 22690,
"count": 11,
"total_bytes": 4730,
"max_bytes": 430
}, {
"kind": 22545,
"count": 11,
"total_bytes": 4697,
"max_bytes": 427
}, {
"kind": 25050,
"count": 10,
"total_bytes": 4394,
"max_bytes": 465
}, {
"kind": 37195,
"count": 10,
"total_bytes": 7494,
"max_bytes": 771
}, {
"kind": 22601,
"count": 10,
"total_bytes": 4280,
"max_bytes": 428
}, {
"kind": 22795,
"count": 10,
"total_bytes": 11639,
"max_bytes": 1487
}, {
"kind": 22891,
"count": 9,
"total_bytes": 13331,
"max_bytes": 1490
}, {
"kind": 22655,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22911,
"count": 9,
"total_bytes": 13328,
"max_bytes": 1487
}, {
"kind": 24133,
"count": 9,
"total_bytes": 8250,
"max_bytes": 1534
}, {
"kind": 13194,
"count": 8,
"total_bytes": 4112,
"max_bytes": 514
}, {
"kind": 22647,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22695,
"count": 7,
"total_bytes": 10339,
"max_bytes": 1481
}, {
"kind": 22653,
"count": 7,
"total_bytes": 10368,
"max_bytes": 1487
}, {
"kind": 22455,
"count": 7,
"total_bytes": 10339,
"max_bytes": 1485
}, {
"kind": 22543,
"count": 7,
"total_bytes": 10354,
"max_bytes": 1487
}, {
"kind": 30315,
"count": 7,
"total_bytes": 2714,
"max_bytes": 398
}, {
"kind": 22676,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 22575,
"count": 6,
"total_bytes": 2550,
"max_bytes": 425
}, {
"kind": 22689,
"count": 6,
"total_bytes": 8851,
"max_bytes": 1484
}, {
"kind": 22913,
"count": 6,
"total_bytes": 2586,
"max_bytes": 431
}, {
"kind": 22774,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 24242,
"count": 6,
"total_bytes": 3474,
"max_bytes": 579
}, {
"kind": 22808,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22842,
"count": 6,
"total_bytes": 8891,
"max_bytes": 1488
}, {
"kind": 22603,
"count": 5,
"total_bytes": 2130,
"max_bytes": 426
}, {
"kind": 6,
"count": 5,
"total_bytes": 10814,
"max_bytes": 7298
}, {
"kind": 31990,
"count": 5,
"total_bytes": 4305,
"max_bytes": 1321
}, {
"kind": 22384,
"count": 5,
"total_bytes": 2130,
"max_bytes": 426
}, {
"kind": 22699,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22613,
"count": 5,
"total_bytes": 3443,
"max_bytes": 691
}, {
"kind": 38502,
"count": 5,
"total_bytes": 1925,
"max_bytes": 385
}, {
"kind": 23003,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22743,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 29333,
"count": 5,
"total_bytes": 2460,
"max_bytes": 492
}, {
"kind": 20004,
"count": 4,
"total_bytes": 1800,
"max_bytes": 549
}, {
"kind": 4,
"count": 4,
"total_bytes": 3072,
"max_bytes": 826
}, {
"kind": 20387,
"count": 4,
"total_bytes": 2056,
"max_bytes": 514
}, {
"kind": 22816,
"count": 4,
"total_bytes": 1716,
"max_bytes": 429
}, {
"kind": 20000,
"count": 4,
"total_bytes": 1872,
"max_bytes": 628
}, {
"kind": 22934,
"count": 3,
"total_bytes": 5535,
"max_bytes": 1846
}, {
"kind": 1985,
"count": 2,
"total_bytes": 948,
"max_bytes": 474
}, {
"kind": 31234,
"count": 2,
"total_bytes": 89092,
"max_bytes": 44546
}, {
"kind": 10777,
"count": 2,
"total_bytes": 976,
"max_bytes": 488
}, {
"kind": 4004,
"count": 2,
"total_bytes": 2338,
"max_bytes": 1169
}, {
"kind": 9735,
"count": 2,
"total_bytes": 3926,
"max_bytes": 2135
}, {
"kind": 1000,
"count": 2,
"total_bytes": 3415,
"max_bytes": 1712
}, {
"kind": 25195,
"count": 2,
"total_bytes": 1356,
"max_bytes": 678
}, {
"kind": 30311,
"count": 2,
"total_bytes": 2210,
"max_bytes": 1198
}, {
"kind": 25196,
"count": 2,
"total_bytes": 3576,
"max_bytes": 2214
}, {
"kind": 1040,
"count": 2,
"total_bytes": 1920,
"max_bytes": 960
}, {
"kind": 38383,
"count": 2,
"total_bytes": 1532,
"max_bytes": 815
}, {
"kind": 30515,
"count": 2,
"total_bytes": 5152,
"max_bytes": 2576
}, {
"kind": 30091,
"count": 2,
"total_bytes": 1221,
"max_bytes": 611
}, {
"kind": 30088,
"count": 2,
"total_bytes": 3717,
"max_bytes": 1859
}, {
"kind": 21617,
"count": 1,
"total_bytes": 494,
"max_bytes": 494
}, {
"kind": 30380,
"count": 1,
"total_bytes": 454,
"max_bytes": 454
}, {
"kind": 22611,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 30079,
"count": 1,
"total_bytes": 875,
"max_bytes": 875
}, {
"kind": 22767,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 38500,
"count": 1,
"total_bytes": 711,
"max_bytes": 711
}, {
"kind": 30312,
"count": 1,
"total_bytes": 461,
"max_bytes": 461
}, {
"kind": 7368,
"count": 1,
"total_bytes": 1039,
"max_bytes": 1039
}, {
"kind": 22967,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 39101,
"count": 1,
"total_bytes": 533,
"max_bytes": 533
}, {
"kind": 445,
"count": 1,
"total_bytes": 4130,
"max_bytes": 4130
}, {
"kind": 10000,
"count": 1,
"total_bytes": 1722,
"max_bytes": 1722
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 22526,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 20787,
"count": 1,
"total_bytes": 926,
"max_bytes": 926
}, {
"kind": 443,
"count": 1,
"total_bytes": 800,
"max_bytes": 800
}, {
"kind": 16,
"count": 1,
"total_bytes": 780,
"max_bytes": 780
}, {
"kind": 10011,
"count": 1,
"total_bytes": 397,
"max_bytes": 397
}, {
"kind": 22679,
"count": 1,
"total_bytes": 693,
"max_bytes": 693
}, {
"kind": 23042,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22514,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 30023,
"count": 1,
"total_bytes": 8164,
"max_bytes": 8164
}, {
"kind": 5300,
"count": 1,
"total_bytes": 630,
"max_bytes": 630
}, {
"kind": 22605,
"count": 1,
"total_bytes": 692,
"max_bytes": 692
}, {
"kind": 34237,
"count": 1,
"total_bytes": 418,
"max_bytes": 418
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 38421,
"count": 1,
"total_bytes": 1069,
"max_bytes": 1069
}, {
"kind": 10100,
"count": 1,
"total_bytes": 1036,
"max_bytes": 1036
}, {
"kind": 30080,
"count": 1,
"total_bytes": 925,
"max_bytes": 925
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1214,
"max_bytes": 1214
}, {
"kind": 38225,
"count": 1,
"total_bytes": 1012,
"max_bytes": 1012
}, {
"kind": 11998,
"count": 1,
"total_bytes": 465,
"max_bytes": 465
}, {
"kind": 22236,
"count": 1,
"total_bytes": 747,
"max_bytes": 747
}, {
"kind": 22823,
"count": 1,
"total_bytes": 1852,
"max_bytes": 1852
}]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,998 @@
{
"duration_seconds": 503,
"total_events": 7884,
"total_bytes": 12205231,
"unique_pubkeys": 2301,
"relay_count": 1,
"relays": [{
"url": "wss://relay.primal.net",
"events": 7884,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 10,
"connected": true
}],
"kinds": [{
"kind": 1059,
"count": 871,
"total_bytes": 1657832,
"max_bytes": 55121
}, {
"kind": 21059,
"count": 740,
"total_bytes": 2308644,
"max_bytes": 22354
}, {
"kind": 30078,
"count": 622,
"total_bytes": 2651879,
"max_bytes": 55090
}, {
"kind": 30383,
"count": 520,
"total_bytes": 278610,
"max_bytes": 548
}, {
"kind": 20001,
"count": 386,
"total_bytes": 403111,
"max_bytes": 2026
}, {
"kind": 22734,
"count": 357,
"total_bytes": 152451,
"max_bytes": 428
}, {
"kind": 1,
"count": 303,
"total_bytes": 303990,
"max_bytes": 4648
}, {
"kind": 30382,
"count": 288,
"total_bytes": 137207,
"max_bytes": 484
}, {
"kind": 0,
"count": 189,
"total_bytes": 140594,
"max_bytes": 2012
}, {
"kind": 29333,
"count": 165,
"total_bytes": 81180,
"max_bytes": 492
}, {
"kind": 10002,
"count": 158,
"total_bytes": 571188,
"max_bytes": 42385
}, {
"kind": 22236,
"count": 158,
"total_bytes": 120802,
"max_bytes": 801
}, {
"kind": 5,
"count": 154,
"total_bytes": 73847,
"max_bytes": 664
}, {
"kind": 4515,
"count": 154,
"total_bytes": 463562,
"max_bytes": 38797
}, {
"kind": 22743,
"count": 134,
"total_bytes": 57352,
"max_bytes": 428
}, {
"kind": 7,
"count": 113,
"total_bytes": 62452,
"max_bytes": 727
}, {
"kind": 24133,
"count": 102,
"total_bytes": 132440,
"max_bytes": 7334
}, {
"kind": 22684,
"count": 99,
"total_bytes": 42669,
"max_bytes": 431
}, {
"kind": 22850,
"count": 94,
"total_bytes": 40420,
"max_bytes": 430
}, {
"kind": 22838,
"count": 88,
"total_bytes": 37752,
"max_bytes": 429
}, {
"kind": 22774,
"count": 84,
"total_bytes": 35952,
"max_bytes": 428
}, {
"kind": 23003,
"count": 84,
"total_bytes": 35952,
"max_bytes": 428
}, {
"kind": 22876,
"count": 83,
"total_bytes": 35358,
"max_bytes": 426
}, {
"kind": 22611,
"count": 75,
"total_bytes": 32175,
"max_bytes": 429
}, {
"kind": 25555,
"count": 69,
"total_bytes": 281319,
"max_bytes": 16987
}, {
"kind": 4,
"count": 66,
"total_bytes": 72028,
"max_bytes": 17018
}, {
"kind": 22613,
"count": 63,
"total_bytes": 43489,
"max_bytes": 692
}, {
"kind": 25050,
"count": 61,
"total_bytes": 24461,
"max_bytes": 401
}, {
"kind": 11998,
"count": 61,
"total_bytes": 28269,
"max_bytes": 465
}, {
"kind": 443,
"count": 49,
"total_bytes": 68056,
"max_bytes": 22480
}, {
"kind": 30301,
"count": 47,
"total_bytes": 53284,
"max_bytes": 1912
}, {
"kind": 3927,
"count": 47,
"total_bytes": 25051,
"max_bytes": 533
}, {
"kind": 20387,
"count": 44,
"total_bytes": 22616,
"max_bytes": 514
}, {
"kind": 22761,
"count": 44,
"total_bytes": 18832,
"max_bytes": 428
}, {
"kind": 13194,
"count": 43,
"total_bytes": 22043,
"max_bytes": 514
}, {
"kind": 20004,
"count": 42,
"total_bytes": 22266,
"max_bytes": 549
}, {
"kind": 5300,
"count": 42,
"total_bytes": 25437,
"max_bytes": 637
}, {
"kind": 22546,
"count": 39,
"total_bytes": 16614,
"max_bytes": 426
}, {
"kind": 22714,
"count": 37,
"total_bytes": 15836,
"max_bytes": 428
}, {
"kind": 30166,
"count": 35,
"total_bytes": 17904,
"max_bytes": 589
}, {
"kind": 9735,
"count": 35,
"total_bytes": 70344,
"max_bytes": 3538
}, {
"kind": 23334,
"count": 35,
"total_bytes": 15084,
"max_bytes": 432
}, {
"kind": 1985,
"count": 34,
"total_bytes": 16232,
"max_bytes": 483
}, {
"kind": 22816,
"count": 33,
"total_bytes": 14157,
"max_bytes": 429
}, {
"kind": 21050,
"count": 33,
"total_bytes": 30862,
"max_bytes": 1874
}, {
"kind": 30315,
"count": 32,
"total_bytes": 21166,
"max_bytes": 925
}, {
"kind": 445,
"count": 32,
"total_bytes": 120956,
"max_bytes": 9346
}, {
"kind": 22698,
"count": 32,
"total_bytes": 13664,
"max_bytes": 427
}, {
"kind": 4004,
"count": 32,
"total_bytes": 37408,
"max_bytes": 1169
}, {
"kind": 38501,
"count": 31,
"total_bytes": 23034,
"max_bytes": 744
}, {
"kind": 31990,
"count": 25,
"total_bytes": 9675,
"max_bytes": 387
}, {
"kind": 10100,
"count": 24,
"total_bytes": 27687,
"max_bytes": 1589
}, {
"kind": 20333,
"count": 24,
"total_bytes": 19120,
"max_bytes": 798
}, {
"kind": 3,
"count": 23,
"total_bytes": 328284,
"max_bytes": 56750
}, {
"kind": 24913,
"count": 22,
"total_bytes": 9700,
"max_bytes": 493
}, {
"kind": 30311,
"count": 21,
"total_bytes": 22061,
"max_bytes": 1197
}, {
"kind": 6,
"count": 21,
"total_bytes": 49793,
"max_bytes": 12925
}, {
"kind": 10050,
"count": 19,
"total_bytes": 8172,
"max_bytes": 450
}, {
"kind": 22712,
"count": 18,
"total_bytes": 7704,
"max_bytes": 428
}, {
"kind": 22808,
"count": 17,
"total_bytes": 7293,
"max_bytes": 429
}, {
"kind": 10000,
"count": 16,
"total_bytes": 26659,
"max_bytes": 12630
}, {
"kind": 4244,
"count": 15,
"total_bytes": 8460,
"max_bytes": 564
}, {
"kind": 23025,
"count": 15,
"total_bytes": 6465,
"max_bytes": 431
}, {
"kind": 31234,
"count": 14,
"total_bytes": 24051,
"max_bytes": 2268
}, {
"kind": 38503,
"count": 13,
"total_bytes": 7174,
"max_bytes": 552
}, {
"kind": 22630,
"count": 12,
"total_bytes": 11762,
"max_bytes": 1654
}, {
"kind": 37195,
"count": 12,
"total_bytes": 8752,
"max_bytes": 934
}, {
"kind": 6300,
"count": 12,
"total_bytes": 77247,
"max_bytes": 17248
}, {
"kind": 10777,
"count": 12,
"total_bytes": 5861,
"max_bytes": 489
}, {
"kind": 30091,
"count": 11,
"total_bytes": 6763,
"max_bytes": 629
}, {
"kind": 20000,
"count": 11,
"total_bytes": 3806,
"max_bytes": 346
}, {
"kind": 30515,
"count": 11,
"total_bytes": 58344,
"max_bytes": 5304
}, {
"kind": 30088,
"count": 11,
"total_bytes": 20483,
"max_bytes": 1871
}, {
"kind": 22361,
"count": 10,
"total_bytes": 4439,
"max_bytes": 445
}, {
"kind": 30280,
"count": 9,
"total_bytes": 4617,
"max_bytes": 513
}, {
"kind": 22902,
"count": 9,
"total_bytes": 3861,
"max_bytes": 429
}, {
"kind": 22506,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8304,
"max_bytes": 1040
}, {
"kind": 22645,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 38500,
"count": 8,
"total_bytes": 5696,
"max_bytes": 713
}, {
"kind": 33334,
"count": 8,
"total_bytes": 6648,
"max_bytes": 831
}, {
"kind": 30380,
"count": 8,
"total_bytes": 3632,
"max_bytes": 454
}, {
"kind": 22688,
"count": 7,
"total_bytes": 2989,
"max_bytes": 427
}, {
"kind": 22656,
"count": 7,
"total_bytes": 3017,
"max_bytes": 431
}, {
"kind": 22316,
"count": 7,
"total_bytes": 3106,
"max_bytes": 444
}, {
"kind": 30089,
"count": 6,
"total_bytes": 123676,
"max_bytes": 44372
}, {
"kind": 22456,
"count": 6,
"total_bytes": 3140,
"max_bytes": 790
}, {
"kind": 22739,
"count": 6,
"total_bytes": 2580,
"max_bytes": 430
}, {
"kind": 22882,
"count": 6,
"total_bytes": 5292,
"max_bytes": 1449
}, {
"kind": 22862,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 1111,
"count": 6,
"total_bytes": 7227,
"max_bytes": 1698
}, {
"kind": 4245,
"count": 5,
"total_bytes": 2220,
"max_bytes": 444
}, {
"kind": 30512,
"count": 5,
"total_bytes": 7504,
"max_bytes": 1506
}, {
"kind": 22641,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22586,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 4243,
"count": 5,
"total_bytes": 2820,
"max_bytes": 564
}, {
"kind": 22835,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22620,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22298,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22852,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22726,
"count": 5,
"total_bytes": 2150,
"max_bytes": 430
}, {
"kind": 22362,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22772,
"count": 5,
"total_bytes": 4051,
"max_bytes": 1132
}, {
"kind": 22805,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 9734,
"count": 4,
"total_bytes": 3756,
"max_bytes": 1033
}, {
"kind": 22200,
"count": 4,
"total_bytes": 1774,
"max_bytes": 444
}, {
"kind": 31510,
"count": 4,
"total_bytes": 3922,
"max_bytes": 982
}, {
"kind": 22570,
"count": 4,
"total_bytes": 1716,
"max_bytes": 429
}, {
"kind": 25195,
"count": 4,
"total_bytes": 2712,
"max_bytes": 678
}, {
"kind": 22752,
"count": 3,
"total_bytes": 4047,
"max_bytes": 3191
}, {
"kind": 31339,
"count": 3,
"total_bytes": 4410,
"max_bytes": 1470
}, {
"kind": 22949,
"count": 3,
"total_bytes": 1287,
"max_bytes": 429
}, {
"kind": 22358,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22474,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22185,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22936,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 25196,
"count": 3,
"total_bytes": 4938,
"max_bytes": 2214
}, {
"kind": 22439,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22824,
"count": 3,
"total_bytes": 1281,
"max_bytes": 427
}, {
"kind": 22270,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22263,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22755,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 10051,
"count": 3,
"total_bytes": 1317,
"max_bytes": 439
}, {
"kind": 34775,
"count": 3,
"total_bytes": 7599,
"max_bytes": 2561
}, {
"kind": 30300,
"count": 3,
"total_bytes": 5502,
"max_bytes": 1834
}, {
"kind": 22234,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 7403,
"count": 2,
"total_bytes": 978,
"max_bytes": 489
}, {
"kind": 22626,
"count": 2,
"total_bytes": 1916,
"max_bytes": 1486
}, {
"kind": 30079,
"count": 2,
"total_bytes": 1750,
"max_bytes": 875
}, {
"kind": 15750,
"count": 2,
"total_bytes": 1242,
"max_bytes": 626
}, {
"kind": 23337,
"count": 2,
"total_bytes": 1027,
"max_bytes": 514
}, {
"kind": 30815,
"count": 2,
"total_bytes": 75068,
"max_bytes": 37534
}, {
"kind": 30213,
"count": 2,
"total_bytes": 1264,
"max_bytes": 632
}, {
"kind": 22716,
"count": 2,
"total_bytes": 854,
"max_bytes": 427
}, {
"kind": 22492,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30000,
"count": 2,
"total_bytes": 1646,
"max_bytes": 823
}, {
"kind": 22463,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22304,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 10003,
"count": 2,
"total_bytes": 20016,
"max_bytes": 15003
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2572,
"max_bytes": 1312
}, {
"kind": 22233,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22275,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30087,
"count": 2,
"total_bytes": 11238,
"max_bytes": 7325
}, {
"kind": 20078,
"count": 2,
"total_bytes": 1458,
"max_bytes": 729
}, {
"kind": 22057,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22251,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22181,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22678,
"count": 2,
"total_bytes": 1388,
"max_bytes": 694
}, {
"kind": 22747,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22410,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22334,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22249,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22258,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30090,
"count": 2,
"total_bytes": 2588,
"max_bytes": 1515
}, {
"kind": 10312,
"count": 1,
"total_bytes": 472,
"max_bytes": 472
}, {
"kind": 22853,
"count": 1,
"total_bytes": 1673,
"max_bytes": 1673
}, {
"kind": 22303,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22306,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22132,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22245,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22352,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22240,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 34236,
"count": 1,
"total_bytes": 931,
"max_bytes": 931
}, {
"kind": 17,
"count": 1,
"total_bytes": 610,
"max_bytes": 610
}, {
"kind": 22286,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 20242,
"count": 1,
"total_bytes": 346,
"max_bytes": 346
}, {
"kind": 7376,
"count": 1,
"total_bytes": 670,
"max_bytes": 670
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1350,
"max_bytes": 1350
}, {
"kind": 22355,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23333,
"count": 1,
"total_bytes": 571,
"max_bytes": 571
}, {
"kind": 22737,
"count": 1,
"total_bytes": 556,
"max_bytes": 556
}, {
"kind": 22291,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30421,
"count": 1,
"total_bytes": 995,
"max_bytes": 995
}, {
"kind": 22860,
"count": 1,
"total_bytes": 553,
"max_bytes": 553
}, {
"kind": 2333,
"count": 1,
"total_bytes": 659,
"max_bytes": 659
}, {
"kind": 22219,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22382,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23032,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 7375,
"count": 1,
"total_bytes": 1822,
"max_bytes": 1822
}, {
"kind": 22579,
"count": 1,
"total_bytes": 1670,
"max_bytes": 1670
}, {
"kind": 10007,
"count": 1,
"total_bytes": 715,
"max_bytes": 715
}, {
"kind": 10086,
"count": 1,
"total_bytes": 715,
"max_bytes": 715
}, {
"kind": 22729,
"count": 1,
"total_bytes": 3030,
"max_bytes": 3030
}, {
"kind": 22540,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 22671,
"count": 1,
"total_bytes": 3194,
"max_bytes": 3194
}, {
"kind": 30618,
"count": 1,
"total_bytes": 460,
"max_bytes": 460
}, {
"kind": 23118,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 38421,
"count": 1,
"total_bytes": 1069,
"max_bytes": 1069
}, {
"kind": 10377,
"count": 1,
"total_bytes": 664,
"max_bytes": 664
}, {
"kind": 11111,
"count": 1,
"total_bytes": 382,
"max_bytes": 382
}, {
"kind": 34550,
"count": 1,
"total_bytes": 867,
"max_bytes": 867
}, {
"kind": 22307,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 1078,
"count": 1,
"total_bytes": 13969,
"max_bytes": 13969
}]
}
@@ -0,0 +1,938 @@
{
"duration_seconds": 376,
"total_events": 5992,
"total_bytes": 8743262,
"unique_pubkeys": 1142,
"relay_count": 1,
"relays": [{
"url": "wss://relay.primal.net",
"events": 5992,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 3,
"connected": true
}],
"kinds": [{
"kind": 30078,
"count": 495,
"total_bytes": 2024957,
"max_bytes": 54029
}, {
"kind": 21059,
"count": 386,
"total_bytes": 1099128,
"max_bytes": 5314
}, {
"kind": 4515,
"count": 374,
"total_bytes": 781718,
"max_bytes": 14221
}, {
"kind": 22591,
"count": 371,
"total_bytes": 286839,
"max_bytes": 1228
}, {
"kind": 20001,
"count": 271,
"total_bytes": 315643,
"max_bytes": 2029
}, {
"kind": 22734,
"count": 264,
"total_bytes": 112728,
"max_bytes": 427
}, {
"kind": 30382,
"count": 255,
"total_bytes": 121375,
"max_bytes": 483
}, {
"kind": 30383,
"count": 237,
"total_bytes": 126938,
"max_bytes": 547
}, {
"kind": 1,
"count": 212,
"total_bytes": 211399,
"max_bytes": 2011
}, {
"kind": 5,
"count": 183,
"total_bytes": 91915,
"max_bytes": 664
}, {
"kind": 22236,
"count": 146,
"total_bytes": 104252,
"max_bytes": 810
}, {
"kind": 0,
"count": 145,
"total_bytes": 103418,
"max_bytes": 2012
}, {
"kind": 7,
"count": 138,
"total_bytes": 79421,
"max_bytes": 1036
}, {
"kind": 22743,
"count": 134,
"total_bytes": 57352,
"max_bytes": 428
}, {
"kind": 1059,
"count": 132,
"total_bytes": 430222,
"max_bytes": 5285
}, {
"kind": 29333,
"count": 130,
"total_bytes": 63960,
"max_bytes": 492
}, {
"kind": 22611,
"count": 117,
"total_bytes": 50147,
"max_bytes": 429
}, {
"kind": 10002,
"count": 114,
"total_bytes": 510211,
"max_bytes": 42449
}, {
"kind": 23456,
"count": 72,
"total_bytes": 121616,
"max_bytes": 1874
}, {
"kind": 22850,
"count": 71,
"total_bytes": 30530,
"max_bytes": 430
}, {
"kind": 22684,
"count": 70,
"total_bytes": 30170,
"max_bytes": 431
}, {
"kind": 22838,
"count": 63,
"total_bytes": 27027,
"max_bytes": 429
}, {
"kind": 23003,
"count": 63,
"total_bytes": 26964,
"max_bytes": 428
}, {
"kind": 22774,
"count": 62,
"total_bytes": 26536,
"max_bytes": 428
}, {
"kind": 22876,
"count": 61,
"total_bytes": 28880,
"max_bytes": 3193
}, {
"kind": 22583,
"count": 61,
"total_bytes": 26047,
"max_bytes": 427
}, {
"kind": 22613,
"count": 60,
"total_bytes": 43172,
"max_bytes": 1133
}, {
"kind": 4,
"count": 58,
"total_bytes": 61778,
"max_bytes": 17018
}, {
"kind": 25555,
"count": 56,
"total_bytes": 249772,
"max_bytes": 16895
}, {
"kind": 25050,
"count": 51,
"total_bytes": 20451,
"max_bytes": 401
}, {
"kind": 11998,
"count": 48,
"total_bytes": 22272,
"max_bytes": 465
}, {
"kind": 22728,
"count": 48,
"total_bytes": 20592,
"max_bytes": 429
}, {
"kind": 31234,
"count": 33,
"total_bytes": 77859,
"max_bytes": 2612
}, {
"kind": 13194,
"count": 32,
"total_bytes": 16448,
"max_bytes": 514
}, {
"kind": 20387,
"count": 32,
"total_bytes": 16448,
"max_bytes": 514
}, {
"kind": 24133,
"count": 32,
"total_bytes": 23680,
"max_bytes": 2214
}, {
"kind": 3,
"count": 31,
"total_bytes": 167289,
"max_bytes": 61894
}, {
"kind": 4004,
"count": 26,
"total_bytes": 30394,
"max_bytes": 1169
}, {
"kind": 22816,
"count": 26,
"total_bytes": 11154,
"max_bytes": 429
}, {
"kind": 20004,
"count": 26,
"total_bytes": 13878,
"max_bytes": 549
}, {
"kind": 30315,
"count": 26,
"total_bytes": 17657,
"max_bytes": 872
}, {
"kind": 443,
"count": 25,
"total_bytes": 43504,
"max_bytes": 22480
}, {
"kind": 38501,
"count": 24,
"total_bytes": 17819,
"max_bytes": 744
}, {
"kind": 1985,
"count": 24,
"total_bytes": 11435,
"max_bytes": 483
}, {
"kind": 22882,
"count": 23,
"total_bytes": 20949,
"max_bytes": 1455
}, {
"kind": 5300,
"count": 22,
"total_bytes": 13434,
"max_bytes": 701
}, {
"kind": 31990,
"count": 22,
"total_bytes": 21777,
"max_bytes": 6861
}, {
"kind": 22790,
"count": 21,
"total_bytes": 8988,
"max_bytes": 428
}, {
"kind": 21050,
"count": 19,
"total_bytes": 17510,
"max_bytes": 1874
}, {
"kind": 10100,
"count": 19,
"total_bytes": 22108,
"max_bytes": 1589
}, {
"kind": 20333,
"count": 18,
"total_bytes": 14340,
"max_bytes": 798
}, {
"kind": 30088,
"count": 16,
"total_bytes": 29798,
"max_bytes": 1871
}, {
"kind": 6,
"count": 16,
"total_bytes": 28131,
"max_bytes": 3369
}, {
"kind": 30091,
"count": 16,
"total_bytes": 9842,
"max_bytes": 629
}, {
"kind": 6300,
"count": 15,
"total_bytes": 45643,
"max_bytes": 8157
}, {
"kind": 22658,
"count": 15,
"total_bytes": 11909,
"max_bytes": 1161
}, {
"kind": 30311,
"count": 15,
"total_bytes": 16556,
"max_bytes": 1197
}, {
"kind": 445,
"count": 15,
"total_bytes": 45070,
"max_bytes": 4130
}, {
"kind": 10777,
"count": 14,
"total_bytes": 6840,
"max_bytes": 489
}, {
"kind": 9735,
"count": 14,
"total_bytes": 27231,
"max_bytes": 2777
}, {
"kind": 4244,
"count": 12,
"total_bytes": 6768,
"max_bytes": 564
}, {
"kind": 22456,
"count": 12,
"total_bytes": 6808,
"max_bytes": 790
}, {
"kind": 38503,
"count": 11,
"total_bytes": 6070,
"max_bytes": 552
}, {
"kind": 10050,
"count": 11,
"total_bytes": 4882,
"max_bytes": 592
}, {
"kind": 30515,
"count": 10,
"total_bytes": 53040,
"max_bytes": 5304
}, {
"kind": 30512,
"count": 10,
"total_bytes": 15210,
"max_bytes": 1596
}, {
"kind": 30089,
"count": 10,
"total_bytes": 164434,
"max_bytes": 44372
}, {
"kind": 10000,
"count": 10,
"total_bytes": 16448,
"max_bytes": 4287
}, {
"kind": 22809,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22486,
"count": 9,
"total_bytes": 3834,
"max_bytes": 426
}, {
"kind": 22376,
"count": 8,
"total_bytes": 3550,
"max_bytes": 444
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8294,
"max_bytes": 1040
}, {
"kind": 38500,
"count": 8,
"total_bytes": 5696,
"max_bytes": 713
}, {
"kind": 30380,
"count": 7,
"total_bytes": 3178,
"max_bytes": 454
}, {
"kind": 20000,
"count": 7,
"total_bytes": 2422,
"max_bytes": 346
}, {
"kind": 22104,
"count": 7,
"total_bytes": 3105,
"max_bytes": 444
}, {
"kind": 23025,
"count": 7,
"total_bytes": 3017,
"max_bytes": 431
}, {
"kind": 22896,
"count": 7,
"total_bytes": 3003,
"max_bytes": 429
}, {
"kind": 33334,
"count": 7,
"total_bytes": 5817,
"max_bytes": 831
}, {
"kind": 22835,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 30443,
"count": 6,
"total_bytes": 7297,
"max_bytes": 1260
}, {
"kind": 22633,
"count": 6,
"total_bytes": 6097,
"max_bytes": 1668
}, {
"kind": 22392,
"count": 6,
"total_bytes": 2662,
"max_bytes": 444
}, {
"kind": 1984,
"count": 6,
"total_bytes": 3180,
"max_bytes": 530
}, {
"kind": 30064,
"count": 6,
"total_bytes": 4438,
"max_bytes": 991
}, {
"kind": 22715,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 30280,
"count": 6,
"total_bytes": 3078,
"max_bytes": 513
}, {
"kind": 22559,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22813,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 22635,
"count": 6,
"total_bytes": 2556,
"max_bytes": 426
}, {
"kind": 22746,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 22364,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 37195,
"count": 5,
"total_bytes": 3664,
"max_bytes": 786
}, {
"kind": 22571,
"count": 5,
"total_bytes": 4055,
"max_bytes": 1127
}, {
"kind": 23004,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22834,
"count": 5,
"total_bytes": 4050,
"max_bytes": 1126
}, {
"kind": 22636,
"count": 5,
"total_bytes": 3280,
"max_bytes": 1164
}, {
"kind": 22786,
"count": 4,
"total_bytes": 1720,
"max_bytes": 430
}, {
"kind": 25195,
"count": 4,
"total_bytes": 2712,
"max_bytes": 678
}, {
"kind": 4243,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 22168,
"count": 4,
"total_bytes": 1774,
"max_bytes": 444
}, {
"kind": 22760,
"count": 4,
"total_bytes": 2239,
"max_bytes": 695
}, {
"kind": 22733,
"count": 4,
"total_bytes": 1696,
"max_bytes": 424
}, {
"kind": 22878,
"count": 3,
"total_bytes": 4059,
"max_bytes": 3203
}, {
"kind": 22902,
"count": 3,
"total_bytes": 5023,
"max_bytes": 1675
}, {
"kind": 22428,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 25196,
"count": 3,
"total_bytes": 4938,
"max_bytes": 2214
}, {
"kind": 22325,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 4245,
"count": 3,
"total_bytes": 1332,
"max_bytes": 444
}, {
"kind": 22258,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22625,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 22631,
"count": 3,
"total_bytes": 1281,
"max_bytes": 427
}, {
"kind": 22551,
"count": 3,
"total_bytes": 1816,
"max_bytes": 695
}, {
"kind": 22763,
"count": 3,
"total_bytes": 2988,
"max_bytes": 1331
}, {
"kind": 3927,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 22186,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30213,
"count": 2,
"total_bytes": 1264,
"max_bytes": 632
}, {
"kind": 22367,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22783,
"count": 2,
"total_bytes": 1391,
"max_bytes": 697
}, {
"kind": 22616,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22268,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22279,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22474,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22407,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22729,
"count": 2,
"total_bytes": 854,
"max_bytes": 427
}, {
"kind": 22234,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 10003,
"count": 2,
"total_bytes": 18782,
"max_bytes": 15076
}, {
"kind": 30815,
"count": 2,
"total_bytes": 75068,
"max_bytes": 37534
}, {
"kind": 1111,
"count": 2,
"total_bytes": 1775,
"max_bytes": 922
}, {
"kind": 30087,
"count": 2,
"total_bytes": 13286,
"max_bytes": 7325
}, {
"kind": 22256,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22299,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30090,
"count": 2,
"total_bytes": 2911,
"max_bytes": 1515
}, {
"kind": 22260,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 7376,
"count": 2,
"total_bytes": 1340,
"max_bytes": 670
}, {
"kind": 22012,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30618,
"count": 2,
"total_bytes": 920,
"max_bytes": 460
}, {
"kind": 1078,
"count": 2,
"total_bytes": 28397,
"max_bytes": 14219
}, {
"kind": 22313,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 31339,
"count": 2,
"total_bytes": 2940,
"max_bytes": 1470
}, {
"kind": 15750,
"count": 2,
"total_bytes": 1184,
"max_bytes": 620
}, {
"kind": 20,
"count": 2,
"total_bytes": 1664,
"max_bytes": 834
}, {
"kind": 22326,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22841,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22738,
"count": 1,
"total_bytes": 1668,
"max_bytes": 1668
}, {
"kind": 22295,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22296,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 31510,
"count": 1,
"total_bytes": 979,
"max_bytes": 979
}, {
"kind": 22691,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 22340,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30003,
"count": 1,
"total_bytes": 387,
"max_bytes": 387
}, {
"kind": 22197,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22257,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30079,
"count": 1,
"total_bytes": 875,
"max_bytes": 875
}, {
"kind": 22811,
"count": 1,
"total_bytes": 3211,
"max_bytes": 3211
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1350,
"max_bytes": 1350
}, {
"kind": 22339,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22395,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30023,
"count": 1,
"total_bytes": 27062,
"max_bytes": 27062
}, {
"kind": 7403,
"count": 1,
"total_bytes": 490,
"max_bytes": 490
}, {
"kind": 22618,
"count": 1,
"total_bytes": 1668,
"max_bytes": 1668
}, {
"kind": 17,
"count": 1,
"total_bytes": 593,
"max_bytes": 593
}, {
"kind": 30000,
"count": 1,
"total_bytes": 823,
"max_bytes": 823
}, {
"kind": 22558,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22143,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22198,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 7375,
"count": 1,
"total_bytes": 2846,
"max_bytes": 2846
}, {
"kind": 22506,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 22243,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23086,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 22688,
"count": 1,
"total_bytes": 3201,
"max_bytes": 3201
}, {
"kind": 22975,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22270,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22584,
"count": 1,
"total_bytes": 1675,
"max_bytes": 1675
}, {
"kind": 22764,
"count": 1,
"total_bytes": 551,
"max_bytes": 551
}, {
"kind": 23071,
"count": 1,
"total_bytes": 1675,
"max_bytes": 1675
}, {
"kind": 22817,
"count": 1,
"total_bytes": 559,
"max_bytes": 559
}, {
"kind": 22614,
"count": 1,
"total_bytes": 3200,
"max_bytes": 3200
}, {
"kind": 22330,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22229,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 24421,
"count": 1,
"total_bytes": 525,
"max_bytes": 525
}]
}
+63
View File
@@ -0,0 +1,63 @@
import json, sys, os
from collections import Counter
def analyze(filepath):
kinds = Counter()
sizes = Counter()
pubkeys = Counter()
total = 0
total_bytes = 0
max_size = 0
max_size_kind = None
relay_msgs = []
with open(filepath) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
ev = json.loads(line)
except:
relay_msgs.append(line)
continue
kind = ev.get('kind', -1)
kinds[kind] += 1
total += 1
pk = ev.get('pubkey', '')
pubkeys[pk] += 1
sz = len(line)
sizes[kind] += sz
total_bytes += sz
if sz > max_size:
max_size = sz
max_size_kind = kind
print(f"{'='*60}")
print(f"File: {os.path.basename(filepath)}")
print(f"{'='*60}")
print(f"Total events: {total}")
print(f"Total bytes: {total_bytes:,}")
print(f"Avg size: {total_bytes/max(total,1):,.0f} bytes")
print(f"Max event: {max_size:,} bytes (kind {max_size_kind})")
print(f"Unique pubkeys: {len(pubkeys)}")
print(f"Relay messages: {len(relay_msgs)}")
if relay_msgs:
print(f" First few: {relay_msgs[:3]}")
print()
print(f"{'Kind':>8} {'Count':>8} {'%':>6} {'Total Bytes':>14} {'Avg Bytes':>10}")
print(f"{'-'*8} {'-'*8} {'-'*6} {'-'*14} {'-'*10}")
for kind, count in kinds.most_common():
pct = count / total * 100
tb = sizes[kind]
avg = tb / count
print(f"{kind:>8} {count:>8} {pct:>5.1f}% {tb:>12,} {avg:>8,.0f}")
print()
if __name__ == '__main__':
for f in sys.argv[1:]:
analyze(f)
View File
+2
View File
@@ -0,0 +1,2 @@
connecting to relay.damus.io... status 503
failed to connect to any of the given relays.
+1
View File
@@ -0,0 +1 @@
connecting to nos.lol... ok.
@@ -0,0 +1,100 @@
{"kind":1059,"id":"3b112c9920bc79584669face207b9491a2a2da388110b84aa085a543d99ac841","pubkey":"c91a8fba68b9d08b9917e3a7443083a9b1364c984685bc56e2310024507612e9","created_at":1785677036,"tags":[["p","0ac458d69b9340c2e8a20a4d5f3afac1211b59e372a646beec37ac32b05ea4cb"]],"content":"v2:v4hmDySAiOH_Iv0uoZ0c5ctYsNwIFjWYQaAr5RPg5s-jqC95LxG56m_gZyio5uKm_7bImZ7hKuyoKXPx8_lfvJsg042FpZnUE8kCU8gAbl0lPTlHbwi-uPL43u0p1SACh1HHf8zyQMLaSBEIV-FTIghxtz9nIjo962bVbCqbh1sk_iMJ6qeEkmmYOCukb8ryRBYnW_7RsruPUsXnbGq5-JS4LXaKwmFmk78GYnaVaRzr_9IlQfUNKe2KD89hcsg5-SbOfLX-YEXeIeB7fKGT741tCtHs9JOBvwA7pgGwdfvUHUmz7WbSv4E6au2JENZ944rkxkv-8LinUAt0vlkXvzmHh-vwhnF9d09BZSj_tHvX2d7PEVg70EgifE6p6X5NnOjILq7VaSYTf2BLfkGl_dwV0R3pGL5_EtHr4abVGCeWTn30YJdUYHu7WV_7YuaX8tAxi0QtZ_5wstZEi8EkIS7R-ocJHNLVX8V3qp8hbFJ9jw_ZZv2Yjfjs_1CZLY7z-1u1WVjcBeXzEGQj5jnQTsf05D9Lv0fE9dmz5UL172LIJhDRQarhtowUJihycoQvRgcY8QH9wZzND-xCclxoncgku2x86TPyMmOPE9vhZX-UeimbJVSFO_PUt71quXF3l6kSLJqGK9Kpjf_yTKlcOiWqRlZ89QYuPfX5jNWEvWfv-g2rLmq2HM59Yq_J7iZZmXB_j0MH0Wz95Vdy6BdFkKA2boYWh7EKTslQHNw7uFZAsbH4keW64urcx1f21y6ccssz3snUGesmxlflSgbQCaY0C4J91OSOU8HsjGwbF1JIAK-UmLwNXsr645Z8iVingbQnSiUyR_RCWgufGaT03bJVp0uZNhrEAHckvYQ15mhHLDSIJoNCiTr0nBfpc1_iupYVQYXjWLgDcFIXhYbrr_cGPL_7sCVrfxKzrCbcm4Nz2LQo2ICrKWFUvWsDOw7Rp8e-gjLaLQxf349rgPPH0bz3VpA_vyiina8G8MGjZ1DnmRRyWn7IqN1Zc2x81_eYu4d25QAeqtctF86keK-nwQcQJt-frFmn9_Ei4uaj56g7YPTIFJF7VLV1af5xf8QfnsDnstmXejfIfq59k6H8-jJKrYbQ3Yue58qTin0wgUOKkPUmq0hml7BeQCykConuV33wV8Y0pJcPkTo69ufeaB8q8t-6xx78-mYNJWLM1KjXLLwqoG6Qqs0-CaASLVYJL7oVFZBpzD1FM8M4h_1d4YC9-_g22ilzm2wu2Z_EHaTOn184oiIpjg80umDtGoNgDejcAb9R8gdnI5SbLo-ianC52UO4piD1DheYurE0sDIuTbbnGgT03q4xtu3xxi-olmPScr3CHvH3mt8wWgjwRGIUgH71yoT0dvPUq6APmqOz_FAMLhFws8rMFaYLzoyzO3knSdwVkNyBqrzapRtP4DiD2pzx8w7mHI6bEpc7zMJZquJ6ksM8g9k","sig":"909df887f9f6ff688a919d16cf49a9cfc542c70e57676a663baa020aa10a28ae01d39aadf79b9ead4ef0f9a95e6af8205ca371b928f2dc498ff5f3b184d75e97"}
{"kind":38501,"id":"73b2e63fc89ae708f89d2d355cffebcdd5bbfcea39da46e6410b408f8b9fe2b9","pubkey":"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3","created_at":1785676888,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676888,\"enc\":\"3d7e99d510750b3af7b20e45c2116c88f2fca84c5147f3c839a83b4be80a982f\",\"meeting_node\":\"92.246.128.180:8434\",\"pubkey\":\"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3\",\"sig\":\"a32af8b0a024dc7a67fa881041ccd4f1c50bcd600d7e8bdac5aadc143a3c2fbeca6ba616ded91df6e92d3669cc8f8114a15778786293e1822f8b6f99867efb55\",\"v\":2}","sig":"b00356fe4f3371d47d3348ca1d5d8d66b92da75ee8f3fe7799fb5744127fa4a76758a97db7bee777b30d4da3e710d6497009e6f9775275ac516f3dbe3cd52830"}
{"kind":30078,"id":"769fffcd14f4044105b49e1233169861c48eee0f156e70a0d3c5bbe4dce4caee","pubkey":"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3","created_at":1785676888,"tags":[["d","bitflash-mainnet-2"],["t","bitflash-mainnet-2"]],"content":"82.67.0.226:8433","sig":"60557df1a6c814e0f815d5cd4d5e8285e7e5a724080af10b5ab0ae382e70a42e1e6af0a9ecf62cbba4e0ba8ff60ef16f485be5ffdc2ee809b7769799a0465a11"}
{"kind":445,"id":"1cb1e652eee8d63d61792139730edae9141329d91c37afb61b6cd5cc3c2cfa27","pubkey":"6bf7822615d8091f8d5b76879cf8e8bef5f20b2a73d81e36bc06bb5b4d5e3034","created_at":1785676878,"tags":[["h","8c3f1c25eefc49fb83a272a826d5a36235381ffdd1ba0c7fa4d774f0c7f1b55b"],["encoding","base64"]],"content":"Ygn9YOUc48RIHWWOgepFIxy0yRbxXB1LWu3Md/RE8sbwnnF50ijaMTQTiVCxlb5s6dITCwpFaz4oPOHIwre/vn4Se9+WXFA2sgl9iNAanzh4ltnzP1c9mG6zQtZRRSYyvCBtBrsAx333eiOKMzSPFov9QqCK0F9Pwz+5Ee3CQpVC/hJfmo0vz4RHOe6WvxW4tjPbGFVd5PrMqeGv8xtMKlAYDdKZIXmRPySxllV7+zH17sN56eRITT/6zRq0enPORGqIAsgQqIZOSe652mRyOqkvkaJ9sxv3IyEb/u8gnZ9sdOuSQs1iRAGQgvLoCKBoTqoIAqMZT6YL/NjhuQ0NzZVZLXJwggfM7rBlv3F0Ad0MgRO6HMpSXxyxKSW1T0/+rgxcvNV7Q3DNOEMOPugCyfmrKboLIHfBSTyFgKdrQ6gZZtwUA72TgGLQctXjCzXwudG+n/q2s1KAItcmG6BpxiqQYAi7iw+fOivUwz41ra8x0hpZEVbU6K9wHwpjhN9tcqq+j6ALdjkFdnffRCMUMXB5GjLubaTdA4MCWZvEGdM5YACnmcCNhTMJMvLzurm3d1+8V/wwQvsIiYIYoa6Ph/ZoXvvE6P/Pvmrg+mARugiS3rSPyE7wZDcTXrhC/gGzBJdLrTW1hDgse5QFh+/m21O0eWJFweOnZSBWYkgDotuyAhzInsHZ4Lh3KtCMnAV/O12j5R78VtwwZHrT4nnejtenNxzIcwmSNpn10kTalHPYWOw=","sig":"a7d756444139f09ebb1c69c38cbd76787d6a2d9d30a2262853f017c78420177fc40307d803b7fe4f472332cfa312f0e0549da0d258648ea58c3792905a2ea05c"}
{"kind":7,"id":"7db435801117613a1eeffa3eaf68a9fc51fbade2951f16b7ddfa0b44ea367113","pubkey":"21c1a52506a343d12c34c0a434f7c6cda276a609ee51a0a02533febf0c84fb3a","created_at":1785676832,"tags":[["e","0000352da8dcd26bb6cbbada87e4ad466090ee86caa2e1a039eb7ee7d9b5a415"],["p","40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451"],["k","1"]],"content":"😂","sig":"865e4ddde04a31a03dd07c7f3db1f87a87dee1057d60497b9f234b1f5d4d2919fe2db33b8810b8a0f557e5fb7bcde1fd2cb3a87336857f7e24333250b3a674a6"}
{"kind":22749,"id":"e1abe91e8827ce0b42b55440d14182531ec10413503e0f0df44bf6ffb1d2ce77","pubkey":"38879d5f7d6ca197f5a43f4043cde208002391eda3d7a66529cc29f95754fb5f","created_at":1785676832,"tags":[["x","14305u172l2k3a2wn1e103y5j6k5v4q69c214a"]],"content":"{\"peerId\":\"hMQ2j8o62EZB1aaiWa0D\"}","sig":"d2a05f9d679f36d1ad95b172ec7343e42f26225621e2a05e8ae79cdd779bec6b0800a74165c66bfc6b3af283a1e1f833890ac87301b15188cd7d6775f86e5805"}
{"kind":21059,"id":"ffc75a95b53a380a2de1114ecfa1426c080d5908df48fc66ce209c429ec8d4d3","pubkey":"a249d052bdf16251537b47555586389c8090db4a08687811edfb8a5dbe574044","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676951"]],"content":"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","sig":"8a6d018e1563b415ed70a13ebd5adf1fe2ee36f92006c570bcfffa198646314280f07fa6788801ce809fcf8e8b5470f629f836076ba19d30600eb273ffbfc542"}
{"kind":21059,"id":"518b7655489f9339f73a7d4cc9013b817e731288dea4b45d2e5eb6485f438489","pubkey":"7b353d5d4669607809fdc4e22690fe19e068912e0aa67c5d83c65ed12803e7e7","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676935"]],"content":"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","sig":"c7c4162fc010944e98390ba22f03d2027dc635c2ec5bc52501d43cec6bc2bd78bd515de180b5586d961d9dc8b374e29597b548c28376b018b8264e28c3860f6a"}
{"kind":21059,"id":"5e8ffd1a6c5f869a3a04aef3bc0cb701930c4348110ba8d52ff0549c0f463d58","pubkey":"79e0b421158103026cfd58b00ed88d52fea107e5974f419879d80a5b7ce3d40a","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676951"]],"content":"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","sig":"bc53226df7c6b32d70168c939b2f810791493f408f365d779e652ab9cf9a88203b25b8d2f40d12ffa29e90101528de9078be4a146317b1ea1ccd80b56af6022a"}
{"kind":38501,"id":"1a8b39de152c04c4cf7d46e9ecd4b05d718cc83f8c283e89c920b5ef91a72a81","pubkey":"bb4f1716ebb55c0cc2759c6ec70be71884486568237be5b0a24fb94b87ab0d5a","created_at":1785676831,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676831,\"enc\":\"d395724b1f1d6cec4f76cb341404015ace916a410ce93993358b6ee568051329\",\"meeting_node\":\"92.246.128.180:8434\",\"pubkey\":\"bb4f1716ebb55c0cc2759c6ec70be71884486568237be5b0a24fb94b87ab0d5a\",\"sig\":\"56006b2ad9357724b0848a25f049343e59177e414550d609a1970900b80fe679335e7fbf09503d5581513676daed140d68313c6d6f0efd075e9b2cd4ca52d8cb\",\"v\":2}","sig":"111a08e451e1733ecbf6fa8e89ad9bff22f9e49bb5059688cdb741db58851656143013b8ee33fe0668e13ff5bc2348764631bad217da59d26ab82775002ab9e5"}
{"kind":21059,"id":"1fa1cc7fb9b195dfb7bb587649e1f57a13c22a716d5b6b7ad1eaa35f3682ba36","pubkey":"df37a8e8a754e2aa7c61085a67f70c11c548381b45f6bfa45c1fe4fc6a05d60f","created_at":1785676831,"tags":[["p","9131564f29a6caa4884f5dc2bc7f2f37cf9f9f3ea79eac6cbee56f23faacfdc4"],["expiration","1785676951"]],"content":"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","sig":"b4f89cad58ddb2840fce5d18b59703412e7781d721482486e53c10a540ca6bb91266bd747ee03301cc4bc638838c2f8e1d19aeb7959597ad3a9c667c808e52a1"}
{"kind":21059,"id":"c6a07a7a6c5efe9492211f5c7ac91af555b672564450a29a13d6c4d480da58e5","pubkey":"33f9b8d989cc0db6caa5503b2e59a174550054c8eb93688641d71371ae305338","created_at":1785676831,"tags":[["p","605e6516fc17fab93c5e1c66534b5d4a8a2b3de64942c3ed742b51626d267cf5"],["expiration","1785676945"]],"content":"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","sig":"291c834d1ba72b4cc0830e8f69bc4a07cee371fdfdad2466d1cfc6cc69b4866cf5e3895ebdfdc43255320a9473423f83f1a2c92d37d7e9acf51e46e344a6e814"}
{"kind":21059,"id":"0f9efae4bd3dc697c547733b74c1c0b2f36c17a14031ad3f9257e35ce8c11d18","pubkey":"a6b8b3720e7a311904a9d6d5ddfcacedd7e0a70654bd41c043072de6f53f8107","created_at":1785676831,"tags":[["p","4f8e06e55adc2dff717adbde066c79a192468858816257c8c717dcaa8d51a28e"],["expiration","1785676951"]],"content":"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","sig":"70c6036ef7b66b2954d3ac4a944f8c5234bad1b37b43a50cfd947fe8e984e0cbf91b3ac51b17aa8babcfadfbd27affe5dee578c36103a9660290e7aabcc814b8"}
{"kind":21059,"id":"233787bfae7c7d89e6d09ee4d1348e59be138204edec57ab399abed267e9ad43","pubkey":"38cd9712528928a6b85f526abf6613917c52c636608e1f7411b33ee457d5bc4b","created_at":1785676831,"tags":[["p","7b938b906ad984ea438f8af5e464ae3c2fcca4acefcfadbe7a3d66a7a69212c0"],["expiration","1785676951"]],"content":"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","sig":"e7894c60e30fdbcb92032f6e2a80090b2474996796771becec02a2e1af748f3c893b113cc6ab197b6fadeceb10805fcdba74ec43617cb665199ebad82209a2d4"}
{"kind":21059,"id":"f22714bcb70a4b41ffd4712f8e5764a0d6bd3af78f6107134932bf8531f52621","pubkey":"fbae19b87545bf7615aeb74ac04cf926a50bf332f3b8c2a0aa5f4c877d36e351","created_at":1785676831,"tags":[["p","fc62a3263abf4a39c09a7b7064aa40480788f868b653c66dc6e3a32c6880a506"],["expiration","1785676951"]],"content":"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","sig":"69949c1cb10548c9a991212c50390aff75777fa2eaf5f979c23f7e031d48fbd0202b7882248552330464dccd8fdd089b613e46e343e540ea49c111005da39d01"}
{"kind":21059,"id":"83cd0f7e4b2e434807599c79a7f0549266bd37f0b91847b0c1cc9681417c9041","pubkey":"b6b05517cc607db1425e192c3ced2df864b46000526d5f50e47b74d25c99436a","created_at":1785676831,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676951"]],"content":"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","sig":"466fcb9413f41abf5337adc2032829603aed33963cdaa304cdf5522bd45b59905298465515310b00588aea216c0aa3249618d60e907a19363bf84bddac67cd25"}
{"kind":21059,"id":"0afb26e3a41a100c304aedc5b80548e679da585e4af81391b7e0196f5eb694ee","pubkey":"d66b7344ecf950b1261dd49de99deea8b325f038dcc1ed9e436b57f9fccefbe3","created_at":1785676831,"tags":[["p","fde8ca9b4a9d1f5d2ed8b82ea13766c37bdbf6cb50bd272d642eaeacc713563d"],["expiration","1785676951"]],"content":"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","sig":"6e8c0633ec51e9d5663a865754116c27582180150f5eb7581570cef47b58449b79315c1623c2684eb4075f46120cdcdecf7fcdd59e57181132e754be7fd8d4de"}
{"kind":21059,"id":"eea2139089ca939ab3e858c7c626609fe629f4dec943497e0aa8bc5c49d13b78","pubkey":"71654ee31d04384b529f095a9cca21b58530ab86c35e239257ae5a6b86b1f70c","created_at":1785676831,"tags":[["p","5376ff74b7bb5a5c9e97cb60a1d068dbdbc5ec39c4346156335747b776ec6d84"],["expiration","1785676951"]],"content":"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","sig":"e03f0a94db48468a6a07bd0ae0a9792c4752f3d498497bbbb3b6fb1873bcea3ba8f2d08efd7e2ca67928a5183f3bf9361699785adbad273a1da60c58cbbd31d7"}
{"kind":10002,"id":"0ff496f20a3bc170afc15a4141498259c26fb487702ba35b9260a96b7b5ba0bb","pubkey":"0000521bbc284afcbc646771c0fda5b1a839a0d3cd7b6f4fe2eda9be15ecbc6b","created_at":1785676831,"tags":[["r","wss://relay.damus.io"],["r","wss://relay.mostro.network"],["r","wss://nos.lol"]],"content":"","sig":"111925db0e99748aedfae54d2442ea8053130095695c5cecf798f3d34cd4039afc4b41662ee128fde49e3c3d517523fe8a828b21d31033c7f4d264e43c2ed35a"}
{"kind":21059,"id":"7f897e9c805c499c963dd8999caf3645b5c6aa968568d5f37462ab8659e41853","pubkey":"6e7e9c51e2739dafb6348212fbf48fb6d768002b018125c8cfbbadfe70b37dd6","created_at":1785676831,"tags":[["p","aaf802c63614f6028fca31eedc3d0021bc3320fc95d0e3af5ddf8e7cae871472"],["expiration","1785676951"]],"content":"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","sig":"24b691e5868fdfa5546625783063d68c5d6bd412e389851e5555bb15b4c6aff3251474fa3580501b8cce93e9711a982dc2f1e7da6ae167ddf459ffec1af0d94f"}
{"kind":21059,"id":"ee1cdd05723f5a6ef4ad3caf9db988f33f9a2921a82cce6f7a24158218ab38ec","pubkey":"755a026b88668c9ce69857b49983a95039dd7689739acabc169a21315ee04783","created_at":1785676831,"tags":[["p","e15d56d1b0ca49d50a4f71cbf04774ae606c4632ad7752195ff9b80c5574e0b3"],["expiration","1785676951"]],"content":"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","sig":"0f74f06f1c14373856bac66f45fbef85bb774aa1e6a7ef82ee821c5dd81b3d32a6666b815d2b53613fb28d7eb63b7a6859c15dcc9ee6b67930ecfa086b10ded8"}
{"kind":22653,"id":"35b97981fe7f50b3f20cae1277b4861b9fa3d49ee7dc7acb8da9d45c1287399e","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","1w3v3i5c503a1e531y6b136q2s5466t39322o4"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"180,27,136,119,41,56,194,227,43,3,188,67,19,22,123,115$ZeuNQlPcrzNPMpEycw20kbmWmAQ9LQA2e4EBLwCM4jl3ajWwnnK7utcxXmapzWDz2xsFhpNRr1jg81viwHLkwzGfOHC4/0o7KKF5Jl4TnLLwSj8g6oToW1FNupDZeu9NkaY0j8JEJCU/G9iTJ/mnnTFSyMZUY9Y3AB5v9+uk+uBij+wmOk1Q4CBDPScFZll2oBNoq/GhlUP9BqhSavsUerOnATJMjqzcMT1m8pZYe8RV5KnCDn6YnSaIlje9LmePx1qMBQBWugymQGuaX5OufdXkvabgjXBMkoj2X1/I9KbAsNGWU2ePqgfGhYXO2IrpEuqwsyiYn9UaZN+RI5v6c847DIpLpVo12xlDg+gog8L1ueTawBSBLazJmCytxqvLROFKCv0EctnbbMKpGu79nsa8NPYcp6c5BVaYFZgWbDxHaEJx2vz85n4STQ8OhjyvA003IwSrE60SUu78URUKA9SXTQoQMbu7H9Uc3+JukT4W3wCMOiMgnJNqQYvYgr2+HJ5EFWLEyD8dlnmI838FJGwnHl05fkAv5vHmdT1Eyrbbstlrn9wrSEmla5mq3cf4lLSkb+06a4D47/j3QBrtfREhnc31O/+XCzhGYZKs5HajWQF3YVW3Md9nHCGFe+yYplq5pCe6HlVSbooXQsYuYyHH8BmfdZCA+Jkrhfbasr+MKTXvvR1ARfXuddxvOjuxk9uQbIN+k1eJmnlyzxODDhMi7gJmQeOpudYWlcquuPhSYHBzKlPs15ibSr/+/Ki/upX0+AZCywGZEyPQE5MYYD6jGoX1SyoZ4jIfl8Xw6oTOTJ+esJc9Ub5LQNBNLUhU8Lh5lxw4LPkZpnepAfyGno1BbrN6V5gg3LA+aoD8pQY8DajozXQX3OWTCu8CGZTu/RXK/VCEnUykrfYaXBU+88+F+3xAxS38G1jdek7VmAMqlfxGY/21roM4zVKahtTJTSZBDK6RtK60tZffGkxOugNHGTY6+DbOAa+NfLg2GWsSkcXSStUmCzFQbql5p4g8FiiO/3udMNlqIx1nxX0DWclYGgHi4DGJw7E/wyuXn7324Tvw/UjVQvm5fHVLc7LnClr//dsQArzagNjdIolCSC+pnI/EUkWAFVwcnwu5RpcPam6c6ZtLkXPhB0bK15vdRY4QUKLNSm0kGDDs3HC6/xi6oZQgF6j49ziOGeV5YYS42XVk41mZG4cGqX9R1EWGd4ZQFiWyRXNLttaMol2CJTQ+yJC1DYjD2N793qoMRuzLVXStld6436buBHJrqL4Wzywzi6eS6ANXyX4jqKxQlXOl9nTxCQ==\"}}","sig":"849aeadaa749a4d431cb91f8a282ac09b9fb322872ff0895430f978ee69108deb02f0cbd323a4b0a92148d4014d8bc8c503e22f9b05569d7d751824737661f9a"}
{"kind":22842,"id":"a497bc4e9330b78979645581cb7422c54b64d990b295d18fb965c15c8b28e0e4","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","3b47662o2j5h6w4431116d4l6k6z6z2o3s2tk51"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"167,11,114,204,116,88,224,166,141,164,164,64,134,197,97,18$zMoeDUlzCvWcmm9Mn8HB1tuqvdDd4BvM3uJBCzwaUKQ0gnUWY+HmKkpaABQex/UCuKjf0fdsRr7SHE3MQ2j4vbSabKo+LD9mdyeQt1vDGpiu1uWbq/Em0HVEEWxTTTAuySuo/cV34xa4On1Ju8LjUaaF2+Bf5udktCf+/EzzVZmGWlyWqbMmw1mg1D3UcSsye2IBiD0FpjDzRiqsweHRoqfESL6XuL5XZ1ka2oipiN4t3DtkIf+QncvSElMm4lxyOnqDR2HDcuQuJKsi9NtuXbZoyJ+rgdfbHAmKsT1hrywjk08aBI56JBUQvVv1ClKq5gRplDRscxlj4CYeksMayW8cHjxqchUEtPdqzOvvjbS/fS1hzEwplaQbYqkEINUSxCx0llT5023uwSMcZXFuPPzEpuu9L91zI1EwBYIjpIeUti2bDG+RtseCcdp8TQh6Ygwqc8Ne5HDzYQHrWqEQRSuurZp/w7+tES8NlAAmPSCPoPLyyKG57uvaWKysmRr3bL1jPdVS+Wqo0WbUMvrS6xEwYle1qvnK/6bP3JeaMhnriq6xTJiMDBplL21vOC7qwvA+sQgu/R/w7q3qcYFre9HbXipFTWY4x8QOGv/JMAf9YSSHmDYUsIKt0Rh3Ow6AJhJjC4bjem6QKpuznLwwRlWcv0sJaF0LtibH/z2bwKs6Q4OozajwS3aJ9YupJT1NcNBOdf0+9R3zZEENOghm8mbLIN0YIkSZXepR/gzn8D7fAt3Bh7gihZvZHReMytPWL3Hjoev30HO8ehBLGbJrpe+gkoxsnbU/zQT40ucy3Vp1KlbOCd7aGGSKqWvlFxbz+EraunwiLtp6gqML257CdGa03iZFKu9joOS98uSzUf3CRn9A28cbgHPvLTQ4UYj00TBb8v04uW6Kv1s0VCm0kZDt1V6moMlFGXu592B6I0aDBkWEr6/uKX1UymIsRBm3Wj5oWXVf8VEvDOYExMFJtHmUJg/z3/KUJASjoX+LAENQO8k28vlWh8b41YTVkA3sB06SZIRGoCVXcEs6+IJgqvuLv58OvZrfK20gSdGEeYdEWwy+w+4zJeaL3OZNbDJZIHCBRRvruSqiXG0OdKT2QnknrJ9otReAc/mN8E4wPwciDUsKV4278gWSZrFWuIPERcf7xxwmbxc/gXCE2bf84V+khN2YH15WtGBDErRNAbVfEwQdrAYzyGKiy3CaoxIIbOn/wR6WPFnImdzMlT2zcIlhLgTHCGo+5186vOk1M/AWsUyah36Pf0eQW3aCtb/nweENd/aGWqSaFzX+L4BHYuKSsT7stFc=\"}}","sig":"5358909a435dd89d34a1e61e94e5294945eb1eb021242dac22067aac7f0652228d9b4a7793a2b9956f879d41cbe70793eb2ff79088470ee3b6af3b01e99d136b"}
{"kind":5,"id":"6e783cf2000cb5793a94ae2b09162e1d3f1304be6eb7c774b852e66b59fd521d","pubkey":"1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8","created_at":1785676831,"tags":[["e","1f4d0d3d55c0fd387950ea4e0de7e973457039bfb03b824cc71b13bbcd39f293"],["e","3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a"]],"content":"","sig":"8bbc6c7ee40ccec7f402c48a5230f71bad3240599ab841e46a3e155841953edc52de47f96fd5b4dd35f8f784e1cc4fc9062303395d6d049d2c78744bb5c176ef"}
{"kind":5,"id":"df31df8ea137659a43641ec7aa14f85f6935907ae71e0ac42d5196f1c7f3022a","pubkey":"a6cd35843a1471debb1b174b5bab472d9f5a2e2391e92207c2279468c3e9ac9d","created_at":1785676831,"tags":[["e","3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a"]],"content":"","sig":"fcbe7b0f5cf5fe044cd295ce6ae5970ca9bcea40e0584b212c027426a1721c3f15c60e387b6f390c7568e7c68e6153512302fc3898a27b0094c2062409788dbb"}
{"kind":21059,"id":"bb18168cfcd10c7cfec63ae32e39d5c01ff248531c05ed8d42851751c56a8c85","pubkey":"99f289641b017b67d71e948bad494b63bcd016724e95e425bba176bc8cc03cd3","created_at":1785676831,"tags":[["p","29d1d9e11edbdbf04538d90d053b7b2d32c1c253fc3df1893c11b68826630609"],["expiration","1785676951"]],"content":"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","sig":"3504f1566dc03ab9fc19e597da5baf4a3734fdcb2f7a24316d0a5f43c37bfcd54e6f9bd1d4a0d8960d1ab5d38d77d7078ee990a2c874db0960300731266e1937"}
{"kind":31991,"id":"f8a2a8a05db114a7096435a285c45498846a0306d9538e7362b9c9665032b6db","pubkey":"537f2bbfdce17c83ee7338dc26229edd9b26226a2f258b578f91f4213611afc3","created_at":1785676831,"tags":[["d","537f2bbf-v1"],["s","available"],["L","agent-reach"],["l","heartbeat","agent-reach"]],"content":"{\"status\":\"available\"}","sig":"09a021823e02d5c2ae3ddda7d05c22e05033012645617cdd6313b5b425b491cc32c61d849d704c7cd9ff294c757d8e0ff26d553f457cd3d7e26c158a2651552c"}
{"kind":22543,"id":"75e596503238aa0f8c242cdb2011667c813515e0f02b006c34026105f67dc2c6","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","456a1r2o456h222f3g036k594s5x2333l2v45"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"36,99,139,51,68,73,226,255,159,72,228,209,82,242,77,19$m/DD2pfpEe+VV4XvRyouNbCVzG2hxkrSt+3zBRI1H8ihRyklrKo5mkvQvvG97YeFy79GxjZhneAlrf1ZzwSoxO/KsE7AtZiuYI/onBlne1ueaqNZca0ZLs/eVvLA3nksV0gf4IMc8g7zJ/IXHGK+9Ha2cSW81dPPlnIsL/7DPyzvtOTpTqENqXYwkpOe5yYa/lOhEF7XNWHDcJCiDonINlMc9JFvXqd+rXlBmrXMsI2De1l4EPGHAk0hnY04uLbpE9j2AFHNMU7JaLLfYyAB2hL4GEjxCqGXuCETm+oJ6hGfsmMeF1dHFfVj+JqsVZAy+pWYr81I5F8xzzhIuLI1DN0mmC0hVV+dDTQw9AzNSoSR/ANICZxkDD6OLeepNkhLZ/h7p5sd/ue5BWONH7mBtKFZ84e1c/OTwJyteTPTokoifMWS3AS1CLAau6Uy6QFXtFz/WhmtVj5MQ1axmzM9asKaJghl4dOi7ScPYLXTluWuAJH9azpjrw9qL4PR9nJG6chRoJAbkRYAodXhT5LmVe64CyWS8GDrAjGOgqY5rmWMJQJgjPJEFVh7xX5yP6tok8z/7K53Rj2XtsCXE5yM+7WI1/l/GSv0Pv7A5LRt/8mndt7tUtg2ez8UyfFsnuZDzPlWwkWCO3jLHLSPvLxYK+X6CiPHKqcZDuvGqNQUTYC0kT0Dhv7ZBPZvVDYgvooA9KgPYT2J1um0MPctIgIzk9TmXCT845IL4aKWOwwMPFkiHc49btdDzCPmEJ9IAI++yUb1QYcnXRhe02jcMcj2t8x9Bmdu4cW7c9syePqWYpytfgsAhCnbT9gfqOoLkrPxmvNP86lbTYLq1KcFFIYIqOrG5dmV6kQQlvbzQbaP+zIk1I3ACOh2KYd1CZpE2FiSPZ9yq7dZ5LZdKd38tM75/LPDQwrLNKvg/yvm5CAMXWNpEeCSfLYUvhSKmFgYVlBLd1aUODOma7pWuRH9A+3V2Sg3rqn3ux6yHASkqvVZN5zQZ38lwvHgfc2SsoB5OkAXwAKPKC7YajsoU9p3VNHvdf6vfL12udGeFODqGqcKbTHTMJC4Q/U4/Ch40HMFbnWdTIK7+Cr4bdQVsML5kUNEqxFSoIO1mcADBxXFP4rIG/tB9GzyQZjmSHJ3bnKvBtvVih+vZZMju8cxrFtW5qedAcDw2qJhhN0dkDRQPOdbq7KkgPktbqe/j1D7NbetdGH7qJ4eeCVb4W7MZkWoNJdXCII16vh8Wv6nzZBK2ItYyY+sKGv0LHR/g76c33zyNzkGOV6AeKb8/wMHfrq1/cqut2T9E8vAPLU=\"}}","sig":"e8418147619db01eacb6f22e5d363226a5b650b2df0b2facb3c33b8c93951c08d63f76a2441beac1facb213e569ce0ba3675907d8d2348d9f1631b568aaa6584"}
{"kind":22668,"id":"d05164ec247e35c2dccc14ae8700bcbcf5355d1f9fb589b97939d37f97f7b4ad","pubkey":"c938fb345106fb08fe6680f7bbc7b2e635967c636e763f6579accb3d0627ca73","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"LE1qO6qizx5jMA2erFhj\"}","sig":"708e7d3c91e43ab8f65630208c294307d2a0472ce5c8e57cc6ba0d548dec4a0ad6ca5858b5de1f98bf52ad7f9c5ff86b61b2c80775edad0a8e251d24d1f0c9ee"}
{"kind":22668,"id":"f10a1eaab979692852318eade7c2fcf8ba3cec9f659867011647580bea0bcd99","pubkey":"129614faaeaf11f6606d45e39a591fd02f1a3d297cf5911c39a007c7c7f3cedd","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"ZZEfCmLfVXsZXTgS8Wil\"}","sig":"fc92eda249f298ad247000192e68a7260e25ee237429cfec40245d4c674568b6938bd9a3ee9795a432fe81e5565425802a037f2bbaeb66d83d46977c968021ea"}
{"kind":9735,"id":"80ef4548d68d36095f62d0c6081b02a021fff27d628262cf4bfce334f8dee039","pubkey":"be1d89794bf92de5dd64c1e60f6a2c70c140abac9932418fee30c5c637fe9479","created_at":1785676830,"tags":[["p","e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81"],["bolt11","lnbc100n1p4x73qupp563fjv7p2j9qlh4cmkqhty5808avtjhxvr90mc4rhqxtpfyly8ysqhp5qq5hwkrnpgm97lt4sfh7gt82ccspge9mvwjrr45y9ej6a80tsnmscqzzsxqyz5vqsp59kuq05evhglk4j8efpcklc78pudrs3tux6slfjjnk2pzdgam343s9qxpqysgq25yxnew6a0humrzhg4fym7xtrxy8f4s23vfjert7wq4rkp88pf8qhe9kpagpt9qaf37w0z3ugfnjeqg2pqew0y2keythw36l6f36t5cqvu99dn"],["description","{\"kind\":9734,\"content\":\"⚡ AQSTR Reward: Btcnews earned 10 sats for like\",\"pubkey\":\"6c27a5aa02b769c6b51fd7edd80c5b21b9ba32c16d913b2bc8e1617fb4573680\",\"created_at\":1785676828,\"tags\":[[\"relays\",\"wss://relay.bullishbounty.com\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.primal.net\",\"wss://buzzbot-relay.bullishbounty.com\",\"wss://at.nostrworks.com\",\"wss://nos.lol\",\"wss://libretechsystems.nostr1.com\",\"wss://btc.klendazu.com\",\"wss://nostr-pub.wellorder.net\",\"wss://nostr.bitcoiner.social\",\"wss://knostr.neutrine.com\",\"wss://nostr-verif.slothy.win\",\"wss://nostr.cercatrova.me\",\"wss://nostr.einundzwanzig.space\",\"wss://nostr.wine\",\"wss://relay.westernbtc.com\",\"wss://nostr.roundrockbitcoiners.com\",\"wss://nostr.land\",\"wss://nostr.mom\",\"wss://purplepag.es\",\"wss://relay.lexingtonbitcoin.org\",\"wss://relay.snort.social\",\"wss://nostr.semisol.dev\"],[\"amount\",\"10000\"],[\"lnurl\",\"spritebeet37@walletofsatoshi.com\"],[\"p\",\"e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81\"]],\"id\":\"6e64b14ed5da47c6d4ee3caf4649dba7e9259ff2173fe9a092d46f838dcb4984\",\"sig\":\"08396838f0645989cba3c7ac753aa5a39cd7ea113ce588f4ca39b1061f1c17cfed46206b91545912de5470ee556d08fb2218f34593cc8abc2f7e317961407dde\"}"],["preimage","833bb4c5c573dfe24cea6306294aec8ee1bcf903c4b1af0af174aab14bbe91ae"]],"content":"","sig":"64460054919c2703eac063ff7dd4807610f123dfa6a3a5c838e87ca7686e9d8c697f5f135dbd4088d1688caf0f79869603b80ccca2b6c7a8f32d6622554d8651"}
{"kind":22668,"id":"e93ca07e3919f3f216dcf0a2e0a96a016840eebf41e5fd706773903af39c84e8","pubkey":"dcb7a55095f3761726ad6d5a53209c3ac46cf9be9c8e5013313fce6de948ee33","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"qLShkXb42nycV1mZOj3N\"}","sig":"de497859d5a80968957b9cf61624e6ac7451d71249cca0a3f2945e888453946aaaf1d42844ab01e620f27c0c1f612d8eea41395449b4354def8eb6f1db91997a"}
{"kind":21059,"id":"6275550f617b1e838bb333227f5567e49c5039508ca2fbe67392df600352fc4a","pubkey":"b6e091b72c36a10e1b33f22e798bc2c497fddaf4334c8b90d9064cd40c969da5","created_at":1785676830,"tags":[["p","2e26907e7e535fadfdadbb3c5c2ca41f39c3f86a78143642fd0e435a4a6da6e4"],["expiration","1785676950"]],"content":"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","sig":"c80ff23a16b2aaa5607fd4930c15fc2b5dc99f5e56d4cb38e4f17d1322fd9f337eb2e82573fd6cf79f7dd1effb1facbed1b429cca561500052474979e5b87050"}
{"kind":20004,"id":"a44cef5e7a3d808548ac3300b5b214d2307c957b1b02c106bce6988fe2a760e4","pubkey":"4e8ec641f5340254c1228c02d69995ca9246c7b3b0295328cd6ac418efbbd9fc","created_at":1785676830,"tags":[["p","04504ed6eed7a46e07b708e8805dcfc2189a49108e3f42ff1137d879f21b6824"]],"content":"zDcS1eEbhLUg6zfwDZ0vRCaLCfKkltMD0OL-e69jva-BPkKQrHIrpJITgSq36FBl-cTK9wyojnUUzld76_OJDBzin-8fJSU?iv=CZo2CiPoKnVD_9S8dF11mtAOomcyZYQn","sig":"cf941df89b47a2144d2650a8c6313c976845e42d856136be7e932ddb5de816eb84ccc725f9237aa4f0a6d46f7148422bc8e956d133022bbe021ddf4f50a71fb9"}
{"kind":21059,"id":"5d90f55f4d87f435f650e0da26e501e7d2376c74b56e259ab807ed3d89f4b313","pubkey":"90e84244c692310d3b93fde994d7a25112a00613f7e6ae8d18f141e66930ca8b","created_at":1785676830,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676950"]],"content":"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","sig":"c1ddfe11c41e265d28ad6787a6f81d5bcbcb0038367424bdf047c78dbd349bb375c6c4a758a89644faecaf94b56d8851da1006e7259a255a42cfd539268a5abc"}
{"kind":1059,"id":"6293744ffa82db6aa84f749cf1a40f069297865f4f47e4b9fa998afdafc91d62","pubkey":"1ecbd9f1a29cf784475500f5bb060692c168beb8a73e7857b90ca4e9e39235ec","created_at":1785676830,"tags":[["p","15df570d3edadf50eefbe7d46ab9896356e52b7464774cfa865262f912cfd577"]],"content":"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","sig":"dc3abfc7a4d9a7689a82a945cd48dc7ea1f315254928111ec8a477f108fd28b59220c76377174082a5311790fbafd13d58018394399e28f6d949af95c4e59f25"}
{"kind":21059,"id":"6b85fbd7cd052c7f39df3de5dfa3ab7959f2a7b2131c22c9f1d03b9f3d11cbcd","pubkey":"61612c62844bb7bb89a96d59667be268c8db7f5a6dc4dc1dc915769929704f93","created_at":1785676830,"tags":[["p","48092008b3f8aedbf147268ae9a191bfd003e97d3521f706d738494d55a05079"],["expiration","1785676950"]],"content":"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","sig":"0c74c118a80bca7ee3ba466eea0cd2ba45c1877f8cfce631185524e2a40b7bbea4f01fd2a4728c1ab4d503ec6d2b5d4438c74314ae18a7e506f7dd804f86f59a"}
{"kind":5,"id":"e004071b98835065bd79466e9694e922eca9fcaf1a9699756da6ff7af1d50f9a","pubkey":"f32f0405b1976474d2eaa74ecd15c257dc7c5854e405996526023a7aef7f9ff0","created_at":1785676830,"tags":[["e","c3d841341e03f0f22b2e6e7cd0811954f7590ff69c785965c2afe1e91cc6558a"],["e","e7558455c28020c88895334d03cfa2567922e7010873686a5d988b73fdc412e1"]],"content":"","sig":"1931a895da1d66d643a2c3ea0e2d84f7c84b75be0c480f7900db9925ad0ad939efcf188d443ecfdf318649b5c5485cba9630d283a285416fc9b137153c4a52fe"}
{"kind":13194,"id":"a3fe853699b7641349db48aaa2f100a6d3c1c4cff0f7d6fff89fc1647d6a2a32","pubkey":"d03ca1662e2bebb12d280299b09a07a9c77450b7c5b6eacb357e3bacde1e14b4","created_at":1785676830,"tags":[["p","d03ca1662e2bebb12d280299b09a07a9c77450b7c5b6eacb357e3bacde1e14b4"]],"content":"pay_invoice multi_pay_invoice make_invoice lookup_invoice list_transactions get_balance get_info","sig":"000f134426656ad3b8b5a4c4b1cd2a208bebf2392dee1763a339d76ebf4585d847ff8fdb0793fb8fd7aed2cca83a78ec21d57635447d353ba5c528a351974123"}
{"kind":22668,"id":"c705e88ba63b117f281ef394600d2f33a03c72dd67c22b8517c689e00853d722","pubkey":"651ef346f974c18f3a781247a39ad0c1812f7817f52c3e1e48d09fb277c62751","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"gnZTneCwL6Qb5ZD4UewN\"}","sig":"85550bc17c5328cafd4f1172106e4e7af93122861b8ea3be462271b6747e64b5baaa6c952ad12c2f4d7160b6431aee4dd3df77e850b45212292816f1177c1a27"}
{"kind":5,"id":"1e8a289daa492596bbe1d34fc8486cfda8f284883d0007599c21655e1ef7cb01","pubkey":"c3d2d513091f404497181534e7965d2b80da38f82ac76bdc8faa2ee9b20b7fe8","created_at":1785676830,"tags":[["e","b7e189df82207c6e3c837afe4900dbbcdbed7e8a962fb24bf3a9ddd2158729ab"]],"content":"","sig":"824d9bfa8b1860acb395c869895e1a950b8d067336265f28f164cfa11ba00dbf398913d00e2acde7f9ac4f81c34f13dec78394ad37376e916c28cd9df6574e60"}
{"kind":5,"id":"504765ce365092825d7185424246b11b1b510ae68c663424a47117e64ce896f3","pubkey":"92e744f3d9e8ca5888da31ac871ef9eed2a0b198ba08f417937cbff3030b0000","created_at":1785676830,"tags":[["e","84780070c1d4646031d9d096b035b73fbc5f1c8e8cf88708b0ee5260dba22e86"],["e","b7e189df82207c6e3c837afe4900dbbcdbed7e8a962fb24bf3a9ddd2158729ab"]],"content":"","sig":"f15d8a5be186bee060ed55fb432679cfa829a5e28f7f142b4004d5ed3c5e06cebb8cea9813cea8c391b541131aac750e4c9c3d189451de67068dddf6ef946603"}
{"kind":31990,"id":"b982613c77be9bd1202a48c630833a7949f07ee83fb76d879d4686af2704d8f3","pubkey":"867a67b1f464d9099e009e73f93ef6e5df8091be0b149bd38c85a88d29af7cf0","created_at":1785676830,"tags":[["l","hello"],["expiration","1785677130"]],"content":"","sig":"88bd7ab0d68817ee099e8d24673091a41f817f1f3c8bf849d2c91d672a35a3b191dfc52ddd485b889b6a6fe8c9654d95ba31591e32029f4490bbdd222526429c"}
{"kind":22668,"id":"22af285ccb3f6287cdcf363ebb06b648add8ebdda93d4ad49a40330abf8250b2","pubkey":"f50ddc7a8ab4a5ae2abc2802f7e33afc40f32acc157c749665cd6b78454690a4","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"MUzRfOSvVHCPwAQ1zQlC\"}","sig":"77518b61f5756b7fcaccfd68a9c63164deea9558b0bf9fdab40d79fd9036b946848fc85241dbc797c11762d24b62265da569fcf91244c571cf2b070e53c8dbbf"}
{"kind":37195,"id":"784c21c5b8d1ae8569d4b663711c9579bc92e6417daa76b160ce8fb764c7fae7","pubkey":"522bf69c8bcc74bcf6e8ba3dd4e2322fce057892ad9b9f81ce93f05e65ab7564","created_at":1785676830,"tags":[["d","iris-chat-device-sync-v1:fdacc68213faacb25010d10c2c6bf9770a3a6006696a8282c906c6bc538315e7"],["protocol","iris-chat-device-sync-v1:fdacc68213faacb25010d10c2c6bf9770a3a6006696a8282c906c6bc538315e7"],["version","1"],["expiration","1785676838"]],"content":"{\"identifier\":\"fips-overlay-v1\",\"version\":1,\"endpoints\":[{\"transport\":\"webrtc\",\"addr\":\"02522bf69c8bcc74bcf6e8ba3dd4e2322fce057892ad9b9f81ce93f05e65ab7564\"}]}","sig":"591246b446dadbe3a2067fd458b72edcd7db27fa9889b22a5b032137bcd14c814c8039041d7ebc1678bb73fc0b8baf5285e54ea09276db25ad1c713ac48c4ae4"}
{"kind":22891,"id":"c4f37c58db11bd3c2d90196f859b88254d8953e85ea2e319b7367755c825ebe6","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676830,"tags":[["x","6y2v464t15574k2a276u1z5o2kw4m6i6d222e1a"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"202,14,200,159,96,222,82,153,64,34,246,116,22,182,211,172$FIiGiFA+45swhd9X7BA8TJjG/LGiO079N5BFevU3Otgzgz8iB+/cHqfFuKfuzApWtHPEqlVp06wXUEaLjvnbAkR6EBEaZP9ImobFwwLkw70gvM0zegLQCxJNxFDUOCvZtONUTjyFneKIqKkM06DTRhxOMKiuYKPR0C1RpvTIwfCnZv89I4+bQPyc56SdHLrAVX20SZO0YHJqe9c6cf7BGLlLHythEawKUNuqs9KK+8KLzQ1THHOBWjYBkvho8bBAzChGkx/tuqt58JsBjkCDbrV1QbhV2sfA6e0ypFMR29KLBU26mQLbMllAgdv8YCY3ceD6a3pLQ5njmMZrQyIno5GC0OWO/14efdYHF88iDknVdgprL2zhL00emLviW14b+lV1UnuedFROYBbqzidDIadnNIPoJO3bxuUIf/Kb+P3oKV+etfnz0/86cY3vuno28op6ixoNjt93v+3bsdZtNTVto6v41qRWj9H2D8Ap2TYpqnAyvZNCl7F67K53ONVxqw5lC2DooGfsUP7nbPBztZrkWHqH7pZ8y390FqEBM+QoqlYT3p562xAM64LYUe3ynEd+cUVOdJn9WjD5qIbk0dvl3N6ykV/IPb6lhWBN6zDrldR+gbZ6muhmJf/LOogRZtEVmYqvy/ObJIJukPBqTCL2TEVgPlmhNgH1FXzl7tIRlp1mI+aIHJwXdaAL4w7pbxP/QxNKru1WUAB9HJI2gasx0NEuFLwD16noWeZeSZfbZSgmKE7qQgHLIRRkp5qWk5AIUaUG0YTiHAaTaH4HakK/pEx5+/i2Ybz4BqdlADCDCHH7ys9/D7l7Kt1UoEKL2dbwrSFjAM2kCyVy8EHz6A0ikXv6Db8yEVLKgfc5dYGAgoe9wtHxxFmAuOq8yq0yVMKDvXoJCN5FlVNh0V62GXE+2My/BY9j/VxgOGYnXbRPEEaMTGqTX4RH1+a5hM8aDivK13BIXMC8j0/F3WMZOW8yP9WDGugWAlMb9Q8yTwVy9upG1XMXFJtCUSGHEhx00GDr3nJZ4QHrjpu/zAIvrBwXWxA2HNMumhbx0lYXUa6Ldpm7CoZMDQIXcSEzypxi/DD603pObMSPJE0pAyLIDdlTJntl9DW9Qmsff3ywVTAlc5dZEBaDmurZeretP7VGP4Q4dx2kpfHrUH0Bm/sNJLRZJIFuLseaOOxDn3s82f0eHNBrrNPorNEUOo0BHixswtEcOO7sYfYUvLVeD239zw47wVvYD3l3oPx22sbNG4i8d4v2B5QvGZYzdS0RTJUKgfJwIoYauUCYIzvhkwZskQvyKIlXcg==\"}}","sig":"292aee436ed0aa9625cc416ad5aa90e2b01729a096d66a13b31d7883fb8cc40167b6f12b90d6c206559d03812b347f85c64cdc00bd91692279b8ff0189930ce5"}
{"kind":1059,"id":"92d46f9c73844d0e73a4da9678fba013135470043952ee249b0a1b36189768bd","pubkey":"af36a76eabe37abb29d257916b8004817163931e8d7acb6e14ee83150b6b4b7b","created_at":1785676830,"tags":[["p","f6ddda433221cd4512a1dd7ed37eea610422964044cb96a739ef15f568670367"]],"content":"v2:5D8IFWcX1XB94nMgs75pjKMWV-qH7kJKJ7cp922s7b6dnXZNRUjTXSY07Zf5cDRkU-NUGfrixmrKSeCH_9m6Oboo65haHfHck4IMZFVikRtsb20ZmDKWmFz_IchtRETAbjUBrVFCA0Pz1M_VkVtH59RjK4E1J7ij4gw0lUacIKVaBau-0ujZEouYRLXtEPKVLuxXI780Gya2OONw_m352_AU638i_KGasfa9SMQZZdlR5E1YcYJi6-sSuonmpYdqrq62EXOue5ovHujxiTxC3Ra8P2xvcMH_VwI4-_e8dt_EQ0EG8WkdqQTEwETT3h_eed-KkuCDXda68mqpbywpJSzJD_R7ys79x5vEwkvjUiEFKHiixaXDAm-s6ssYxTVc7J7vPkEFkYIaPqi3ClS5Tbnf8Iv4eLngmFrABUtjGip1XHZ_2QVVv56-2qIjra3-4BBKMSq7qm2eQslTJBgbnuh9zxdjfhqAw7yefff4-hVCn2VBTr3EQdd2bsILheXzWOEj0vePQ-OAG1WPqWD3ruiXBsJPyJs7VWsPP2_PyBfrGWCvamVadPdekc-vPjPrX1qkJustPn1m5AOTW_pFmC8p-zVe-HTPTyNvtkXZe4oOIDBTK2sctviWcrrLF_3gQewIlOD7_KRFPUA6LSMXcK3Ns7saa-jhVfxNIz0nYeW7VgxLbhgveozh7B9b9kHTgTHOcQe6_XEAa3wSHXosYYRJUe0NVMwUN5OC-mmzwCYniyDnWbinTqOdx_EqVQBOoX1VPh8eMqEv499HWnbALCn4iNcY0FA1Qe9KanHpfC-VF8Hz7hz0KB_ZaqNCUSTl0SC_vHmoeEnx_h7Shyn7vdloBXvdaaZjgDfp0otcrC_oa6ERCZg2YEkwqnmytmHOVf6zoY7OsOoRFTTQtpvd4cintjIqVg-V_WvN9M6Bth6g7iTL8Uh1aTDq2efEOeto5GHVGIQftL_uMwSjAAlphCv17a_EreXQM7V55f056Z5OIQAbOS-tXkoZ0O9SRgQurDFsc5mC39H1GoVrpPofIGUFFAgnCTB4mbuw-V3Y8xm0_MJG-xEE4fr7ymp2syDpgCedy_QkFgU9-0fv5FaDH11uWs7ulJlZ1o_WHuzC02ydpOUK_2FseAWTYw1KhN_9ddes8d7xBRF8Ta8pLPbVBNYwOitHb17cspuv1LSind9bjT0bH6dBx0vD22tHRIvkieIuHjtypcz8U5NRVmbchmw4WhpvptvWUoVqySxibpLBNTbUVqlBxXJole6_qkpnDOn1qkGuJTN-tWqQfzZyhXCNULWZSlZ1NYpBrSgTQTO6QBh_tr9oe8Fc-oip7_c7o9gxcvYtmTgghqW7meYhi_tb6dO9b4JM8VAj9VoiFPd6gBXNSWTJ2ej36aqqzF_d_KEgpVr8a01-4owIZMPTsefZCMLzoMux-q_aYfvtvhs3wM7YYHWfyIw","sig":"52aa16a82ed4922de3a8c78fcf5be1066a328cd99ef0b2268e3133e9cf575605c3c23b42d6c5921c00e19718ad446eea051d4fa601527694f01257fc53f830c4"}
{"kind":20001,"id":"0008627e93710f902251efbebea4f25418fb8263eda4cf113ce40d49100593e6","pubkey":"d4dd33c3e92280a7cc8a4bd65e5b3876a572218902921df450db07badb1bb00b","created_at":1785676829,"tags":[["g","td"],["t","teleport"],["n","Boob"],["client","glub.chat"],["nonce","1534","12"]],"content":"","sig":"5175c979a4127301f00119c041ddb30a0b2836485a1d05d824aa933c84a4775ef163588118cf8d9a09f805860341df300ee4a8e9471443544946972497e0e80b"}
{"kind":37195,"id":"b3848e2644e70a784d948eef64eca608ce57ba1a8a75ae5e26565ddcaa9bb8f3","pubkey":"14b1d34a9d862fa6073e2f9ad689ff11af8411751f71f10e5440cde3876c6710","created_at":1785676829,"tags":[["d","fips-overlay-v1"],["protocol","fips-overlay-v1"],["version","1"],["expiration","1785680424"]],"content":"{\"identifier\":\"fips-overlay-v1\",\"version\":1,\"endpoints\":[{\"transport\":\"udp\",\"addr\":\"nat\"}],\"signalRelays\":[\"wss://nos.lol\",\"wss://offchain.pub\",\"wss://relay.damus.io\",\"wss://temp.iris.to\"],\"stunServers\":[\"stun:stun.iris.to:3478\",\"stun:stun.l.google.com:19302\",\"stun:stun.cloudflare.com:3478\"]}","sig":"be31af4686ebfe3d850c516be763e74f2c5588a19427dcee1577b9a3b6dba59da5ac920fc6c6a524631e9e9bc32161b8de01116f34758055101c7cbf406c6995"}
{"kind":21059,"id":"7e8ac972c6a94ef4fd8a07ced607d6229065d421d6c480fc13e7a40979f981a0","pubkey":"7be527855bd9034f673f08570a74b91411141a1c07b20f0d096386f1f2552a91","created_at":1785676829,"tags":[["p","4571e6236c45bb75ca1bf6c142b996d9d451ec6c3fa14ce279dd53a827b6525d"],["expiration","1785676949"]],"content":"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","sig":"8af0bb1316d7577439f261ad76875b1babedb47262565eb2a8276dec88691a80dd64e2a894dbe78562a4b69696141d860ec6c9f890a14c84549a1225a38fe47b"}
{"kind":22795,"id":"b0806a8559de7756b708116bca32573e9a11d6bbdf0a51fcff4cff7628d7a6df","pubkey":"984094d4207e9130fbb6c28bb438e2cfce471ae19c71046d6950fd57f20179cd","created_at":1785676829,"tags":[["x","s1c1b5i40406k26b1f1f5y3s3f5o4x4l534s24"]],"content":"{\"peerId\":\"5LAzUDGlviJBcbB5kzJl\"}","sig":"eee99b6279b298bf32d5bd110c24176560e6144d36d134fb2c981eba9c8bc803378ccd154a31620938d1d90a9d9dd248be70321ebb4943d1e96019d40244d190"}
{"kind":21059,"id":"27bc4a662ebbc319927530f64000150760e270bdce69cb12b442529ac33ca680","pubkey":"73399d3a149703031fa0d4701ea9e319226c2979343e89c4915230a533572656","created_at":1785676829,"tags":[["p","fe3cf170bd88be377f684d98d098aeffe3438882eb2a81013ddc4e66ea00dc46"],["expiration","1785676947"]],"content":"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","sig":"4813c3412e748c9d99e4ec4ccc6b667a4140714a094914df05d111025e442bd30a594714f03cb0df3537ecc82653c7045b98531bbc0cf49faec2bd67a107c175"}
{"kind":21059,"id":"e323e7671e52fa1f93b1c38b74d9045f0e7e0c57c5eb64f0dd9dc0aa4cb7e9f2","pubkey":"42dcdcedf8f9fb5a343aca2f324985960a296f89a40b186ab2663ae544649098","created_at":1785676829,"tags":[["p","db4332cd601e12593fbe239cc5af98f27401d46012c778694c22cb147ae3efdc"],["expiration","1785676949"]],"content":"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","sig":"269bdd6b0a63d8d7842c04b8f6e743848caea5442047c3bf9aef46d389be02b61d6b43a2377b33f6ed6b199c64f2775120447c2281560ed6cdffd14c33192476"}
{"kind":21059,"id":"80b6cea3ad0b0abedd667b18d3804879c9b053bd309d388e58aad701d3c4e129","pubkey":"764da1376080b4c50b442e8682283e911a211675ac019a354eb4abcb3e6febf4","created_at":1785676829,"tags":[["p","c90adab9315b97f7bdd9bff861bb026c9c454be1f1fb3a9d64812b75ac5e2bcd"],["expiration","1785676949"]],"content":"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","sig":"30e2bdf73205825bf3a19ff6d5b0fffe49b2d427470d7670e9464628f58791f491b41f78ac8a137ebbe64fb0d603c1b516e857ce8561c8812e53c04e313cb3d5"}
{"kind":21059,"id":"7d821dae2407dc0ce17b7be76558fea2a7b42332a80fa26a775bb481e7ab4936","pubkey":"51ab81dee29a9a2cdf3adaa4820d00f4018cd540d6c4b6f5b7cd70a155193e7d","created_at":1785676829,"tags":[["p","4571e6236c45bb75ca1bf6c142b996d9d451ec6c3fa14ce279dd53a827b6525d"],["expiration","1785676949"]],"content":"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","sig":"d2ecfdef11672886570bb8da9f00e89ed488351faa377f7cfd9625455b752d6765a1415ef351bce454bb4c8506e9a7795572fcd0ca3799a5e1c88a96ad164287"}
{"kind":22668,"id":"e5bd8d0ba4903407e15dccff8c3847a142403fa8808f08d48927eb1989630530","pubkey":"011290d507f716e7e28b78431c6dacaef737ed0c296eea006b3aeed76162aad2","created_at":1785676829,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"p57BF10Qa2QR5CWwRPwk\"}","sig":"bec46000893446903a21a2cc3c153fd0caeaa2863d420f613d5c86eeeea86252a977c415c08782e52af2dd68e12ede07786e437f4b4e5995d3cc9dab901715e4"}
{"kind":20001,"id":"0f03ec93ee1b132d83fe87a2a11488a2457ee29c32d012ef86d6499be2b6aea2","pubkey":"b5c861e028296358eeda540fdc4bf0a55620fcfbc8402c2aa6c0e374ca4caa93","created_at":1785676829,"tags":[["d","781ea0904b118a95"]],"content":"{\"e\":\"🐝\"}","sig":"8c1f43b54758888bab77b247e125d39cbd0f851a9a866e8cea094740fa972dc3545245e97c3b013a10c4ec85ddd162698c377e4335b17f9e3c5e4c834e5d8469"}
{"kind":21059,"id":"efa8558e04b1018251c1ca9f75ade84c3665236ac0dd4e7531a5aa4ac50f382e","pubkey":"61e46286db0f8b3a657c97a18b24f941c2bfcfee82e4dee7c43a27dc73d9ea9b","created_at":1785676829,"tags":[["p","62cf088f0fa0f198b341ce6c3c4d16f1bf06c4c94b11b54e5e7ca6290e22e94f"],["expiration","1785676949"]],"content":"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","sig":"6d06c924a3cd1d73deaccc52a86ab472a3a30975ab54311f5e3c83d0896f18abb7c921e5726317532de6382bec35f2237c4c225c03abb8cdd52fd143a1c0e93b"}
{"kind":21059,"id":"cac1ddbfca49abfed7106b5e8b35799e803a7323c88f4ab9311c55904dff8038","pubkey":"cc06b791544dbf3924fa4839b43e6db9df75329b67278fd32963cd19e9cee582","created_at":1785676829,"tags":[["p","f3d5c038e4ce5015ff2069c5984c8a47571a427b40e120d5c412e6ae6647744d"],["expiration","1785676949"]],"content":"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","sig":"0648a3e97630d479672873ddc45636b50fda69d79145c6cfed77d518a27808417169dc87ed861eb0e6ca1a8bb274dcd8cf970b37cd3f0d42c09ff7d71be20e4b"}
{"kind":22751,"id":"cc1b2bbb1bd8ef560837d280d1510d93d5199aa019673d00a871aaa14bf4c848","pubkey":"3d5ce431f2987ab0f0ea0745e5703bac780ea9384e90c8a198ed5dc8978f1ea8","created_at":1785676829,"tags":[["x","5bb4v4t492d291p6m82o5g2eb2m4w1iho1h"]],"content":"{\"peerId\":\"KInutrde21v4Es53GmBx\"}","sig":"b3875007e6999506f815c52162492680a92f5323dfa51358a5481174692a5f53cee4f8b6bf295f0b69704c777c2d15963bc2fec70d5cb1238569e3189d1097a3"}
{"kind":21059,"id":"ce16cb9b2bf391663f91c94f158745bc94760d383dabdc35ffe728462aba63e6","pubkey":"d5bccb32f8efc4810802306118e3a2e2bc39b690f6e1defcc06e007b3da9e591","created_at":1785676829,"tags":[["p","2651be916e121c6baa67236dc6882371562f2dba151ddac18948b8edd88c1321"],["expiration","1785676943"]],"content":"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","sig":"67272aed2ffbe1417bdeb2362a9d49e6535253b5792ac600ea3992395b7fed7cae545a5e14ed4a0f037390707f5d0e50c6132b07a480249df008e13e724b8954"}
{"kind":13194,"id":"3c0da6d6cfaa5f198bf2dfda454096968434028808a2e29e760a63a824de62d7","pubkey":"18ab43661d7848e6e3843a09c6aaafb007af2ce2ff3481f81469ddae1e8e8fb3","created_at":1785676829,"tags":[["p","18ab43661d7848e6e3843a09c6aaafb007af2ce2ff3481f81469ddae1e8e8fb3"]],"content":"pay_invoice multi_pay_invoice make_invoice lookup_invoice list_transactions get_balance get_info","sig":"29c9cef993fd1012c2a21691671afd32f057f9364eed31b40f0906348ddcc6606aa5b2e4ef8aaf9198e8cc97488cd4133ae650763a3b8da3293e72e07c9801fa"}
{"kind":21059,"id":"38274f3cf136f9a19359e866d55b86e661b9c1da65c9e826c08f7669e84a7337","pubkey":"931128665954f1a3f0fa09a5e3f371ab192fe840575daaf43040453d838285af","created_at":1785676829,"tags":[["p","2ec845714f95e64d1de364181734c2e58d33e0534c33402d2bce2c0c10a53b65"],["expiration","1785676949"]],"content":"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","sig":"77927d477d07993c9ccf912e488b3384d69aba61b70f13c85cfbfffc2cb3fab7d384ddc4b377470bef2a93ad4635b1ebb74d05dc0c6f3f21aa15d4b6385ace43"}
{"kind":22668,"id":"4d59c8963f927fa9ea3c116a710049275033c52fe5eee1c2d970b1e55a021f34","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\"}","sig":"7a569489f112258890c563d4924c4f7344537aef198f64a7cc0b9f32b96ec7306ea98cfe1fff4fa6ac2a162ffa4944ba3699cf2751bea61b765ab32b90122651"}
{"kind":21059,"id":"5e5b504bc37e7a30f376cf1ecc6884e31163dae41f5ca3af1c7d5462e972dddb","pubkey":"fd5a12fdce164794145f3a5c4b35d798def92f98242ff4609673de9214f2895e","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676948"]],"content":"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","sig":"dd1e19594cf2758b966c27f773e20c5f1ce5127e7a6c046f40df32587a6294e23ce91cbcf789f25ce93ca17408ba16d17a82449d734c80d328ec53f096047e74"}
{"kind":5,"id":"782cd31915d712063037d3948ee8e7aabfd7560dd332227d502ef641cdca1ce6","pubkey":"b454400c0f61af322680e46825a10c69aaa4226be2f474dc21fab2c7271d4ddd","created_at":1785676829,"tags":[["e","9aa8c8f7a20f62091f9b3ba3ac8262c9c2bc1a40d87ad1c75f0683c0e757a983"]],"content":"","sig":"ca9858bbbd3a7d46a9930122981ad8402cb302dd4a1802df2270c115c12971fff8cbe9b740898d5f618f3f0a9ddcf79ff8f3fbfbefebd21d60670ce15982bcf5"}
{"kind":21059,"id":"3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a","pubkey":"842130f8fe9ce8aac0eb1e1f310de93b32e6968a5553dfd63eb5d8eb7874e0c3","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676949"]],"content":"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","sig":"aee0be687ade030557e86ba811b5d03c590fe6cd2a62085015cbb4391960b6a24fd0d0caffc5ebc5ba765006c3d98a5c847b15bd81ef38617d491a6a11cb6705"}
{"kind":5,"id":"137d08e7ffb7fe1cb7bc8b2be596e6b65d5699f7cd64f13a23bf390f70807c90","pubkey":"29d1d9e11edbdbf04538d90d053b7b2d32c1c253fc3df1893c11b68826630609","created_at":1785676829,"tags":[["e","810bde802c36ad6d9aa0e017ac0f8bbd6e66ad80df5b9419c0313be8054f2d48"],["e","9d28fd6286648d315a0c47db82e20b67e985a54f10eef09745d5cf0241b85e28"]],"content":"","sig":"bc21735717ac3f284a372f2bc72ca9d15badca8f7fd8016ee89015ec19d9a212f528fce8b16a98b62cb5108f86b5ae2698b0049eca59f70b710eecc303765544"}
{"kind":21059,"id":"16bdf72f80f5945fb5e08e5f50701615cdf8d9f016c8f48a6f8f22993da33080","pubkey":"7255fdb4025a1afe269132658221bc6e698108cf7b43571e929cfaa712063ebd","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676949"]],"content":"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","sig":"e113e7856c06159dc5cedb6262892bc5014d0cb5fb21e749eb23509413548dc8df95cda93e1dc8833a3987e5227bf51c578fc990f3689e7c0fef9da250230350"}
{"kind":21059,"id":"9aa8c8f7a20f62091f9b3ba3ac8262c9c2bc1a40d87ad1c75f0683c0e757a983","pubkey":"8f3f5b88fc7641fc8cba3f85d31023c07562c05290bd8dcda72efbf444f69926","created_at":1785676829,"tags":[["p","de2474ebae76da5fc0dbbfde5128465ece9adb6174dc5210014f277e2c54a093"],["expiration","1785676948"]],"content":"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","sig":"857148777b63e5e0ee1eeb98b12539466cbcd35b21514484fad507c6230b9e71d3d565af406cd8b97b0966f7e83bb212ae21eaf8a57162b2f47bcac7c293e427"}
{"kind":24242,"id":"443bf61bd52ead74bbf7f3e0d147845f0ed9edf680334029c1252c41a58caa4f","pubkey":"0214d7edd25fe949a9978de3b36f90f7a4ec432dc9753087044be638e7f2d3a8","created_at":1785676829,"tags":[["r","2145c86a56b377b435b1a17888dd93707e0991ed524c613f9ba73541c94e14f0"],["t","P"]],"content":"{\"t\": \"P\", \"d\": \"VeNHJfdrPLMeFtNrqqVREOclr1Pj8K8tan/m+4DPgHeYoq1KI2ryYNcOurYaDy13Cq5YEJGD4EZtB0CxAl4Su48pTzXdmuSs1IHm0lBYtkHuxEhUZigIGi9aNYY=\"}","sig":"c350662d3c997e8238be51564efb0172005507fe2e73f882bae32108fa272d6c4fa2edc55866d899b01ec3944578133092220e5451cbad4d5ac171827ab1744c"}
{"kind":5,"id":"22f5949ec55c1ed8ce97c6d7d2bac71992efccff6b5eec37d4329c5f44b79f4a","pubkey":"a104303328d41ca62add479f54d19467aaf0969ce09024e0d46dd577706c9bae","created_at":1785676829,"tags":[["e","958d7a6b0092f0d0e331b2dbb7f197504de75ba442b7fea806be2098c7c2eb2a"]],"content":"","sig":"dce8d65f9939f32c404db12b3ac29ebf01d28bea8a58346a8d886cd174e799e46d945c97d96bdc0ba4345c5ca44b6e9827cede44ac10efb931bb3f831e8935d2"}
{"kind":22455,"id":"173ac3706d5caf2f883cede799f54e93a1b89dbb4cafa1885703562208a2350a","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","i10614r6s6x481r3l6875fg1631u55a564j"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"214,198,243,224,5,154,106,180,224,102,15,15,79,30,115,171$ff2wqB0tK5EY9LkoZ8yHi/oyz+MfFpi/6sLjRP++zumM+33SIHLHt5lk9bEZeCZGnWaJm/xGeBLkAjrIvGLeCU4T5FZ3vuFvQUWJmo2MmdbCPISD6scgXXpVx854ExqWcJjRrTa6eFY1/2bhJZ1/9gxjPZ+gX4NoXcQpk5FbTFI9scY+tokJCGS/VWIt9on3dKA1ABXvfHUaV49hlTi2VmR1/RZsz6UEzSdz4RJ1erFZer0R12CGhJ5QAg8YTAcfbFe7D1gKQPb2RnFUMNyBEHbJNys9jPnq5sF3QoA8JnxgSZgDs0nttXfmuTtywGqY9zvCNp3kV/n0BatZUlAyzhcPgAqlkA+I6L82hT2vY6Z2uUJOYRi/yiAswm7BU8PaV5vkgViODnqANVnnOHHyDpRrMhWEKmFuenxp5cBs505WkauYI5gSIuAJCE2aWhcQs1jX5uGMU6UsqJkUv83FdEZ6Syi4EEVxT9waIZmBuNE6XOKn2WG7DhY56FZoquzH9b+Nh1yeJiYkBldP5AMQhmzUHK0oP0iF1EfmxW8D6sJEWYhR+3mNkOF+cITYdVbwotN6Ijpitj07yP7/QtzeaWENsEJ4hEz9vODuSkgiFVNX8bwSjwHoreD6O/71DSsHve5WUe8Tld9K4xMv7+Qwe1pnbLea3F8qxEfBAnJVVpVXUZ1u97NuO4bsQ1TduEH+ie2cFE3MKZq16IIz3+8+fjMa0DO0UrxaBa4dX1au6Yxgd0EV42gCEu6uGqSnf6VzTjBwMoGdQsDK4PswZ8M6052ik7MBBYbq18VMm0eqBfhN19R2IFB6s9k7P20gzQYd+r4no4ynERuOyJrGKD9RrSJ8L9Ta8n+5ssFcHOFrFAehxoD82XWUtv2tviwLvjKUlU0ocvbiHAOssJ60cFha0Bt+zJqlPjhjH93MciW6Sk3FeaFsFZteilwQI1sn0f3rmk5p8xXFG7An21gswdcCEXnssKxLi5dQ5CsBD3+qz7sNzxHALi2KnVWZbqhi347XZ8HMcXeGE3HM+bKkKJ/RkJ6c0ofhqAwp5ign3Njtey6hLkFKz6sF9Oh6x6e80ec58nAsIQxRApzLgojJ5lUKVCb7dyhtJpkeGrCNVuVqiwe5dttQVNeWk8HHsbBznLF65OKy7tJ9CjVRibeybSLpNNZPDyFk2H4UGC1jSbwRWQM9kymWRruuIGrUdLLtvHtbakA+RVDItKHh+tuchA3ghtsBw+0x1YTzNg/uewokU6Ts6g1xcYn34PKCuoEbAm7B0xijw8HRaUA7/1lSb6XupDcddAMsdXDR\"}}","sig":"b4df17fe73796261f380dbf59cd4c253691f7df7c5ed02ea7ce17ff08ece4e1244ae692d384577c3d5a4b3a52ed1823f4e21a3b709fb8228966e01fd9cd96446"}
{"kind":22795,"id":"51840075a161979e0960dee2cf985dcff29ce8024542224f1e8d270f05d266dc","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","2t6r5t3g2w4ke3c5q5fe155b4733m3m4d204h"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"223,148,124,74,154,14,67,150,164,85,250,22,207,70,113,66$GQ31axJJQOIow+lfqV1YKLNxUh+V1rAioseBNHgVEhYONSeICwYKTUGHaVGQjflxRs1atY42oGidpCqpxjDXdpUgIQb3/EzXDCEydaqqyLy5l3m25dy5k7ufZFX15VcXR8wcDi0RYRFN/iGE9mSHsLV5w6lQqv3jpTsKAeFYqwCkvzh5WnUwM8PvmR+PYFTXbb3HXYwJp76QGR3POTyJxvijjtjkP0Or3V1JrPLxMDJIeNlNjWi4r5CzFzO79ymTE2/5b2IBlN5FaZbTYxRvICaKVA42gztpDDUFUaOfsS74xCun+wQfnwf4cefm7h8HochY45m/43oZ+B69kHmQlXdzRDtxPyW4Nlhakp4+fpvMuaO+AbAjLuJeKxOXbBdheXjQ8WBJyJU6xXgDmUy/x8cSJtMfshnAR86ENlKSe5TNR4ZGEkDGt8ZibqtHzr3U0lUFhfCNTnqdGhPKPFCr89W6ifbWkY6Ap+8DsiqzBZdS9Jm/rj6dBpADWXu1eCH95TNRUhA344wAGCdLITq+5HFEG3U1+F9e9Kv9anp+QXFRo1DSs9sBdKIzH2GOoYgvv/amzP0t/ujF0jVC50YbJ+vcD2Mk738T/RoSw6fenLHR+vuSo5GEQZKfIqsDnLol9MuHwilistA/EiTl+g/5oVoZfFz9Ka+paRsyE3jVx/r9q0/XqGqCIVGPuq8Zq8Q9upXxHZJ6GDWWDnkbW/Jdh7lzkPEgPk8A6mhv6tKVo9yKPGQvcRC6ir65pvnpr8LkcmGD0ZcmyUJZgcS7QYtHdFr5nEm97uOkDEcjWNsEY4BGuZLquE5YXA/FEKT9OfF9IGANGeFCejUxYC6wmfBgNKPHn4y8dwF4O1VS2dyzvIEMeQOWZfLX18Rfzwer2DUqhy9Vug0UlH85Zg7BO3xsd7HckWkGhaQe9/OoYc/J7qu7kgGGaJEot4tkVx1jqodqfQ8231pFSFYAPzqO7ARdk5Iah+4o5leT7/3etL+NGI0P5NRngirLDwDf3usWIoP8rZHH3X80VvUdSaXfNNVIfGtVtVEoldpJdLMgBjjrZN4bSfw0Vrxq8ARXPF1XhziTKhhUknepY9kinMynYwebwmr9jxFJhFOvrh40Pm1SNAEegoMxrBoz/BfSUuFlo4ZUOdT4H29npvGYrQQ6icPfu3ksOI/a1jEzIlf3Mi97gW3q+rVj4vNVx3PoqOi6Ndbp/vSqa3cMNJXXPfTQ0V8w+4SW/PwyVqnhIl01kqxjNdtdis4EMQ4ceGJ3MDbgCdLA6lG6tGvogy4V9/esn71GfC/fD1Wz\"}}","sig":"88644bb037cd5b75c311efc06f45f39c6e8100615abcc08ed0f285ec9e282733a88838369e318af75e2687b7bdd6d0904a4bb90fc2f16027755d3d060f5e30e7"}
{"kind":38501,"id":"bc1428364c22cadcdda9e97f47a30dd3da07d32bef447c987e007e6cf33b7cbb","pubkey":"8b46fa734a7230497197cb033e6f10e55fa400e90447cfe5651c9ad30a9f7b4a","created_at":1785676829,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676829,\"enc\":\"97f504ab5a031b93d29a813de02ccbec80f0345b11ade65b66cc0f89c8470920\",\"meeting_node\":\"90.156.222.107:8434\",\"pubkey\":\"8b46fa734a7230497197cb033e6f10e55fa400e90447cfe5651c9ad30a9f7b4a\",\"sig\":\"eb1094bc2e5aa1c3d49bb21f9ccd24b96a8066e1efe7400d649c97a03c7e599b1f5c820dd700a3502b675c52513bd0f2bea34a4dc41fd4c101dc621e4971378c\",\"v\":2}","sig":"2053e323b44d3fdff3f956edf9a61ceb3651ee517eea4bf11adbe7e4438fe63234aa7304ed3cf5e13455ef6037576e89c6587a771e514540624b2f4157531e27"}
{"kind":20001,"id":"6c2fac4c1a3d24f3c83db08fd810cbdb92e47b48b9b0bb3e54ff7081882250f5","pubkey":"bf5ceea34cb45bd0d4903e83c62a49ba1c80d589934d83fd4b2c616aa7c3df57","created_at":1785676829,"tags":[["g","u3"]],"content":"","sig":"ad150f48ecf1ccfb096cdcde24a846a8396d1f8586e238a778916836c78a56752070c2833ec655350d9241cabb4785dc4e48501dba5ee1b35ee442b80f677de5"}
{"kind":7,"id":"c138c6daa6626d75a2621f1c15729f63abbc682773bbfb278412b6545131ac5f","pubkey":"e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81","created_at":1785676829,"tags":[["e","f148576d2a0f9754d8006ea10f31531c600ca6abbbb64f92b13146ab32316e12"],["p","c79200d36c345e41d46c135b30077b2f15d43cb286069f1e4f2e65c09162a900"]],"content":"+","sig":"f02914b8b9345f45f4ee7cbdf7e058e3a15c976436df11b71ebbbb7f62d08bfbb8398cfbd202095bf9dd3d4bee6ad597d9289d12c8bf32a8b57323666ccf5028"}
{"kind":5,"id":"05a69cae2f6f03f85d49ce912ffb234405ebd40b4619afd0f48e79f4c9afaf8a","pubkey":"3b6b4b29eb5b65aedb5d5291ed382d64dd62c555911380bd0dd27f160446a025","created_at":1785676828,"tags":[["e","64b710cf656b37c7d2df8935752af4914b88f8bb1ce3536c1315da43b72b6820"]],"content":"","sig":"f62372888a6e0ecc9b865ea00c18425f46984212a929441def9ca841e7216f95e4ceaa6eb39e25dc8a533d3455dbc1191450c053e894cd7a6a752b3026694d86"}
{"kind":22850,"id":"d502724e18c11af192ee65a8798828a5839de91251fb80dde196e09751600622","pubkey":"972698b75bab763abf02e914c05ab10ba92946d949e44994914bc8ec0f0c8356","created_at":1785676828,"tags":[["x","154a6xc4s1134333k6j1w3i5z2u4x2g27363w6q"]],"content":"{\"peerId\":\"E4BBrrzMaIncSQjB5BIY\"}","sig":"ce077337ae276256eb927f006fe5ebd9062272852330909b8e372176ac17e9cde6c75f95a91a8a31971fd50e39d3f09bb78a8469444feaf934e220ffe295b1cf"}
{"kind":21059,"id":"c70dc8831fdbe3c9e881ec501b0164baad462fb4cdf966b232771faa15faf8d1","pubkey":"f6618de562d05fc1544e473cd2de7d19172886a84413f09be3580091b0c6166c","created_at":1785676828,"tags":[["p","d71691e02c10d3564530a86b51598532eef5f3d62317ff93cea2c3b9c4d4526a"],["expiration","1785676949"]],"content":"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","sig":"15cb4aa077947993d94481c3ec275fb4905a66a0b0309ed2962170c104900322ef9cebdfb10c28f55e91d2ea1c5ab66682d570f8146db4b1e35ce71eb465ba15"}
{"kind":5,"id":"4465287c1593ab85700231ff2bdb75b84cdcd7f1110333bb4d615fad2d73b147","pubkey":"fde8ca9b4a9d1f5d2ed8b82ea13766c37bdbf6cb50bd272d642eaeacc713563d","created_at":1785676828,"tags":[["e","5982eb4b4dc892cdaeb1a37d3329c3b11633be2b19401d1e0f2881309be0616b"],["e","64b710cf656b37c7d2df8935752af4914b88f8bb1ce3536c1315da43b72b6820"]],"content":"","sig":"806cc8d8fb44a1b2fddc62950e525f42bcc3dd184ded730235b0c4bc36c13017e86a512f98a3d005c20affd8a546fcdb99ec3c90e4550a47e1dc2034cd86cb16"}
{"kind":21059,"id":"638a053daeb7e0a7a0f36a2e2b1c0e2e36d25c58142139f99f706244fc7c09d8","pubkey":"1a54ab43a4bdcf983de90f5bcb7ec25cb61b7cee8a5a719faa7c7f0abdbbc88b","created_at":1785676828,"tags":[["p","92e744f3d9e8ca5888da31ac871ef9eed2a0b198ba08f417937cbff3030b0000"],["expiration","1785676948"]],"content":"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","sig":"134ad847b73bdec6eb45c66e2971175bedd00fb7a6c1a6c708beaa65fe8d4089fdb58e3c347631cb298e209df1a5183a0b84513e271bb8fd1f7a20696da68c66"}
{"kind":22668,"id":"ddc1eb28e2625a90f93416b40326661d5f5b627056519d83e58625ea117a5a67","pubkey":"645d864f8e4df2e7961010c726ca1dfee79c1f849e89981f38c67954ab27ab44","created_at":1785676828,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"XLa4RppouLPlwZlPEN1L\"}","sig":"9bf8af313f3c5596cb8f4d0696b8d72cd268af14bfaa38b831caafe1248eb58a40af81fb3a43fadd6491b086f811f18fa6c4422548ec58c37fd3d157f4bf8eb3"}
{"kind":5,"id":"0b34123c6c388de6d35d821348dcc9651adb2eec495f020f9bf29e32a9ee5282","pubkey":"a6cd35843a1471debb1b174b5bab472d9f5a2e2391e92207c2279468c3e9ac9d","created_at":1785676828,"tags":[["e","8290a6025d0f18c48f30f52570181ff53bb29b44cb2af6234857b7792650cc17"]],"content":"","sig":"a00555524925631aa1e50be3f5bb87518191259643f46806d55e13a5c791a3f073eb82e8e442b0979f269eaf323c846b8d3e0bb6185f622c1f734eeb444c4216"}
{"kind":5,"id":"290ae3e79db0ee20be75bb4368fcd1ca3e4e97fce13390c135f5a00e24daab6d","pubkey":"f562b3ec661872d7e53a9babc2e75aefcc3d1d658aa32070d7a6e3948dab2af2","created_at":1785676828,"tags":[["e","c75edbfe2657b06d905d775d476618bc97a68398447ed36b5b53e381ff34b99b"]],"content":"","sig":"ad8cb04b8a8d861e079d122c5e459fb9d505a4e024fca20d606df2cb0cd5a5701fff566d0b52e22d978e76eb2bfa8b115a207a1de074dbb307bb8b8c1b0bb614"}
{"kind":5,"id":"da568c14b08dd42c0455f01d8239aade88c77ce1994de16260dd198b308a3421","pubkey":"b454400c0f61af322680e46825a10c69aaa4226be2f474dc21fab2c7271d4ddd","created_at":1785676828,"tags":[["e","098b11ac3a658a5f260afe741b1e702b6ceb795073d0fd67f2b39893c9f5f1a5"]],"content":"","sig":"860316ca6bfbf5032c9086ae56d89b65085e4adb6c083f0f2a5b369cdf4951673b4a4dbd565190b071d12681f11e55495b3a354a41b85a19f68c064d3f010bc8"}
{"kind":22668,"id":"2e19b89f9daadb9436e59f893fbe4cfb06d233052bb1aa2e8e2a5f2ded14bd1a","pubkey":"983451ee5aa44cfd054418a4b4d61389f6cacfe5bc8ad63f247040506a7bc8f1","created_at":1785676828,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"MOfdN8BFCzSXK2ClbLZp\"}","sig":"7a7fa69ff865ca2548336fe3bb0c8fc72033822efc524f28f0056654fd8f70ddf0c4d0bf6266bcea2d320204fd7e87b9304ebfddbbb46dd528816e7a5adbd93d"}
{"kind":21059,"id":"c76184eeacfdd807619fc4cfea4d898e87cd44a25b4f534f22f4de068974a659","pubkey":"756284430945b4e12636df19fb8892364d6b685f15724c3cd4cf8089aebce8e8","created_at":1785676828,"tags":[["p","c3d2d513091f404497181534e7965d2b80da38f82ac76bdc8faa2ee9b20b7fe8"],["expiration","1785676948"]],"content":"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","sig":"8a7c9e47f5e301802cb6eddf0c11109a161c7873aebfc4bdfb6660dd4da3d772d4c9f13ed6708a6726770a19a931b0d912ad7ef9b2289e1471e0503b64a9de9c"}
{"kind":22734,"id":"c4eb84f16a0b91da08b80674809ff770cf2fbbbea535222f71a6f70b1afaae2f","pubkey":"62131eedf03e285f05e7327cd868d6cf04567658e72f09f6a027a2cb526491b2","created_at":1785676828,"tags":[["x","2n2c211h5a65204bmik1t4y663e1k2jq1j2w"]],"content":"{\"peerId\":\"5JyB3m4D09Af6swaeL6g\"}","sig":"b025b0f3d0f2d5de0256a1401816a8393a3109bdb2446b4a4986fde228438a1837a54a3b0d9debda4528c9e840e6acd458daa7d7e88d45c735ebe0149868160d"}
{"kind":21059,"id":"f8bb284d3b89bf6edc85ac8157f4fe146b1fe1201164a094e85dfdd6bc9004e5","pubkey":"8a67ab6ff23ea573d894131bbee2f3b49dddd2cbbe9c532b0ea8bab5dde6c27e","created_at":1785676828,"tags":[["p","2b794b141e7490a4f53e23d41286cecdecdc1bba9aac19dc92ddc3984d361992"],["expiration","1785676948"]],"content":"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","sig":"726a670d5ca1bd6ce671f3c34ec0f4102cf8a9253380154b0621849290fe856b805fb69b772e55cee187879f4a4759e044e679c03455e513764ec7dbc50bdcf5"}
{"kind":22734,"id":"69a3ed5c758ed45a82812314ef3dac047107069f56f1f938dc6e26a0d87ea86e","pubkey":"fe577acc08542c36add5fd831b6ca9e248d98f2a24f9648ec71f42947e289710","created_at":1785676828,"tags":[["x","2n2c211h5a65204bmik1t4y663e1k2jq1j2w"]],"content":"{\"peerId\":\"FjdIMX0nehvvfKeE8uS3\"}","sig":"8c52eb4118de1f91350941bd08b6953d6681afce8158c4fdf2da1addf2e15e07454a671bfb8b5600bccb4544380b3f2ff49161d22330a1138943781392a1ff75"}
{"kind":38501,"id":"8bb6cd8e927c497c1d0d43380fae119b3e1de4bbed9df532ca8c85b956508b14","pubkey":"be43349f0292354b71e5857cd3ef5577e4d8882b50d5853b8d0d2e6f33cb9b17","created_at":1785676828,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676828,\"enc\":\"ae5076051e63594c2bbaca4fbd1dfdeb661276f7bfc325f7b6e6e227068e6248\",\"meeting_node\":\"90.156.222.107:8434\",\"pubkey\":\"be43349f0292354b71e5857cd3ef5577e4d8882b50d5853b8d0d2e6f33cb9b17\",\"sig\":\"9199cc8116a79d3de74e28e1171a5f88f8ba0d3ee58fd620a9bc06b63652f85b9b03e583b4a3f5960a2d56a1f4d9c3eb2731def42d75bd28ee6ed7c65609b4a1\",\"v\":2}","sig":"de12e3a0b7ca656aad163076e2f32e5de2878a55b163e7ddad075e6e8ae9d98d4aac1502bb76f69c03086d4e7350f5d32686dba1295788ceb9dfd4049cc657f2"}
{"kind":24242,"id":"967ecb7f628c4c40e6142b01d1e4fcfc021af09ceffabdf3e6e5616acebbba13","pubkey":"a975b6d0b2376a560d115745432db9828936b586e85baab34b0faedc1ec69cc9","created_at":1785676828,"tags":[["r","5ae4bcc88c92069957e24564ee8f8f0bbc50aa0ec33740fa6df513c5b4261c58"],["t","P"]],"content":"{\"t\": \"P\", \"d\": \"i22abIUK979JGbf+XiW0SEkynRl3oeD+ub6fdvXs9e1HiuSYhHzExoBu9kIDplEXnBZLQ5GckxdvfPVMCI5wLRruGhCQ8FyJxZMN3BOlkS7ExKGTXSCX7ckXAro=\"}","sig":"9ae34fa8b69db93c5469d472d3fe3e357370412926cbbd161c1c65ddf8a01531b0cbd184f46531169d738dc97f9ad647a8ad0eb0b8f768bc1bd05b21e424e086"}
{"kind":10011,"id":"9fc9a8ddbcea381b42dc97807b9eda3e22ef405d389804ad1ed86e2c72f39534","pubkey":"5de310432a95a13ba1905b5c78b511b931fa7efa86bea15057ebe16424cb9273","created_at":1785676828,"tags":[["t","relay-monitor"]],"content":"[relay-monitor] health check at 2026-08-02T13:20:28.031870+00:00","sig":"aa3d24c702a8172c6a28e59ab84ef4e831c7ebe2153f19dea71f92cdb8d4c35495669bc5eb80df2c9c7eea273d919fbce1e26fc226ab22cd4cfa202aae6e0aef"}
{"kind":5,"id":"a9a5a2840cdedd585ef53eb3fb086119b09d6ec97614a19e7014edc8e9fbfdd8","pubkey":"abb85588adc1f9fc70c07c2d6119c7ea8483e05674d85e89fd12fe26f37d7b80","created_at":1785676828,"tags":[["e","6baaf169c5a412c48bc05796ae31420fbf0abf7f4425030a02a5b5fb6e89095f"],["p","abb85588adc1f9fc70c07c2d6119c7ea8483e05674d85e89fd12fe26f37d7b80"],["k","7"],["client","Amethyst"]],"content":"","sig":"49e4211186e0ba933c513078fad6bc0e40c496573adef997be03e343ceff2dc309616a22f44b110ae2d7ce840f969140ac83dd764d4afb0c7e52fcd47442aeeb"}
{"kind":24133,"id":"65468cf8859c2651655aef77fc73e162475671a1b9fe78507a75e8e1b0fce9c7","pubkey":"adc534e19063daed4a5fcffafcabac5c69ea92781fb870b800ada248494388e8","created_at":1785676827,"tags":[["p","8bf629b3d519a0f8a8390137a445c0eb2f5f2b4a8ed71151de898051e8006f13"]],"content":"AnvGOKiC2qxlzPRhokNkl7/Mv18NnRtNUDK3VpuOS8rGYCpUPhpICdZpq+OUZWQ12gFqaWNWEinqLyR8FXRCGKxeJ5iArhWFMF2+IpFe4mCj3Kg/bdu02V3waBzg0T4rAOaRWqsWQBSUz8ENTaeJUUhAyx11INFOWvh4MEh4ablqPY1ONZrMlfOrK5h/rqNQ0b9zbbzoeocfSUKrblTU/TcAa7bm8EnCOGjkyvfkW0PHbIND12NZG/zOBmvQIUTOkCkC","sig":"2e56dfe53f064ddcd45d09ef9211fb7d685cec414fd75537504a023be2fb8ad13b7c2ff34117e1b467902ec7fb207bd5bf17f28f83569ecbbe724e484a560767"}
{"kind":20001,"id":"828803cdb45f3fe3f1d3ad30cec52a0a608cd44aa575eb61e4781236d206dac6","pubkey":"e1ef740bbab4a0d7fa489084a5dc41bd3c829bfa80cacbd2c3fe104ef41b258c","created_at":1785676827,"tags":[["g","u2sh7"]],"content":"","sig":"e3b6b73b3d9583c71795ed5d9a73375cca25202a48f9083c6e2fd001951ab60fbdb03f30ae4958570dfb62999d8c776d5cbe6e3f0278591873ebabf0e260d40e"}
{"kind":21059,"id":"add2aaee09d5b4616d2688c3c1acd0c093f8f3e609676adb8433e7a4802b3d80","pubkey":"362e1183392e4916b8c309fd22d11b54ff7f50fb31f775c4ed7cb6c6da4cb9c4","created_at":1785676827,"tags":[["p","904d65c28c1bc185277f9e60de40e9044650b0c59e2729a4458e3c4ab6ba4efc"],["expiration","1785676947"]],"content":"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","sig":"0b358a00881890d1170b455ad5f23800a8620026939af6e98fa1d12bf5ac052b57347ce0cb15acf46c2b800be9a1e4f4ea331ef3a6186920db69fc9c43c0172b"}
{"kind":5,"id":"8cb0ebc4d6307a3df82ac8e660e4ef9e7988cea03fde286f21277ab17e67991f","pubkey":"0a4f177b087b133099e52e5b50456f0d4c832db60c0f35d3269a6a055f25522a","created_at":1785676827,"tags":[["e","ad604a106024742fe765459c43b7f45f2c7cc3123454d4d517d34c3c539686f4"]],"content":"","sig":"217950b519bd5343ca498e48b4b04637ce09ec405d211913d85201bdc76561d61b6628424ad33dfb607c0150fe63b5197230b66991ce011b8dea3734cba0b66b"}
{"kind":21059,"id":"c3d841341e03f0f22b2e6e7cd0811954f7590ff69c785965c2afe1e91cc6558a","pubkey":"608696506aca54466d337ef6dccb9cc714dd94ca1f88e91939fabec7d3114024","created_at":1785676827,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676947"]],"content":"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","sig":"887844fc5f7ce096fe528ed0a1891992556d30ae6f3e9a06177ce638f2a0dc21433678ad30483ddef40584048986d2d0cc3090975b9c96e13d8ad163b5aafbff"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+456
View File
@@ -0,0 +1,456 @@
/*
* caching_relay - relay-by-relay per-author until-cursor drain backfill.
*
* Each (author, relay) pair has its own persistent until_cursor stored in
* the caching_backfill_relay_progress table. Each backfill tick picks the
* next author that still has at least one incomplete relay (round-robin),
* then queries each incomplete relay one at a time with
* since=0 & until=cursor & limit=page_size
* publishes the page, and advances that relay's cursor to
* oldest_event_created_at - 1. A relay is marked complete when a page
* returns fewer than page_size events (the relay is drained for this
* author). An author is marked complete in caching_followed_pubkeys when
* all their relay progress rows are complete.
*/
#define _GNU_SOURCE
#include "backfill.h"
#include "follow_graph.h"
#include "pg_inbox.h"
#include "debug.h"
#include "../nostr_core_lib/cjson/cJSON.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
/* ------------------------------------------------------------------ */
/* Global access for status reporting */
/* ------------------------------------------------------------------ */
/* Singleton pointer set in cr_backfill_init, used by the accessor
* functions below so the main loop / status reporter can see what
* the backfill is currently working on. */
static cr_backfill_t *g_bf = NULL;
const char *cr_backfill_active_pubkey(void) {
return g_bf ? g_bf->active_pubkey : "";
}
const char *cr_backfill_active_relay(void) {
return g_bf ? g_bf->active_relay : "";
}
int cr_backfill_active_got_eose(void) {
return g_bf ? g_bf->active_relay_got_eose : 0;
}
/* ------------------------------------------------------------------ */
/* EOSE callback for synchronous_query_relays_with_progress */
/* ------------------------------------------------------------------ */
/* Context passed to the query callback. Records whether the relay sent
* EOSE (we got everything) or timed out (partial result). */
typedef struct {
int got_eose; /* 1 if EOSE received, 0 if timed out/errored */
int events_count; /* events reported by callback */
char last_status[256]; /* last status string from callback (may include message) */
} backfill_query_ctx_t;
static void backfill_query_callback(const char *relay_url, const char *status,
const char *event_id, int events_received,
int total_relays, int completed_relays,
void *user_data) {
(void)relay_url; (void)total_relays; (void)completed_relays;
backfill_query_ctx_t *ctx = (backfill_query_ctx_t *)user_data;
if (!ctx || !status) return;
/* Ignore the synthetic "all_complete" status from core_relays.c — it
* is sent AFTER the real EOSE/timeout and would overwrite the real
* status with a misleading "all_complete" string, and clobber
* events_count with total_unique_events. The real per-relay status
* (eose/timeout/error) is what we need for completion decisions. */
if (strcmp(status, "all_complete") == 0) {
return;
}
/* For NOTICE/CLOSED/error, event_id carries the descriptive message
* content (relay's own text for NOTICE/CLOSED, or our transport-level
* diagnosis for error). Include it in the status for display.
* Priority: relay's own response (NOTICE/CLOSED) > our error > bare
* status string. */
if ((strcmp(status, "NOTICE") == 0 || strcmp(status, "CLOSED") == 0) &&
event_id && event_id[0] != '\0') {
snprintf(ctx->last_status, sizeof(ctx->last_status), "%s: %s", status, event_id);
} else if (strcmp(status, "error") == 0) {
if (event_id && event_id[0] != '\0') {
snprintf(ctx->last_status, sizeof(ctx->last_status), "error: %s", event_id);
} else {
snprintf(ctx->last_status, sizeof(ctx->last_status), "error");
}
} else {
snprintf(ctx->last_status, sizeof(ctx->last_status), "%s", status);
}
if (strcmp(status, "eose") == 0) {
ctx->got_eose = 1;
} else if (strcmp(status, "timeout") == 0) {
ctx->got_eose = 0;
} else if (strcmp(status, "error") == 0) {
ctx->got_eose = 0;
}
/* NOTICE/CLOSED don't change got_eose — the relay might still send
* events or EOSE after a NOTICE. */
ctx->events_count = events_received;
}
/* ------------------------------------------------------------------ */
/* Helpers */
/* ------------------------------------------------------------------ */
/* Returns 1 if the PostgreSQL inbox is initialized (PG mode active).
* Probes the config table; a non-NULL result means the connection is up. */
static int pg_mode_active(void) {
char *v = pg_inbox_get_config_value("caching_root_npubs");
if (v) { free(v); return 1; }
char *v2 = pg_inbox_get_config_value("caching_kinds");
if (v2) { free(v2); return 1; }
return 0;
}
/* Find the oldest (minimum) created_at among an array of event JSON objects.
* Returns 1 and sets *out_oldest, or 0 if no events / no valid created_at. */
static int find_oldest_created_at(cJSON **events, int count, long *out_oldest) {
long oldest = 0;
int found = 0;
for (int i = 0; i < count; i++) {
cJSON *ca = cJSON_GetObjectItem(events[i], "created_at");
if (!ca || !cJSON_IsNumber(ca)) continue;
long ts = (long)ca->valuedouble;
if (!found || ts < oldest) {
oldest = ts;
found = 1;
}
}
if (found && out_oldest) *out_oldest = oldest;
return found;
}
/* Find the newest (maximum) created_at among an array of event JSON objects.
* Returns 1 and sets *out_newest, or 0 if no events / no valid created_at. */
static int find_newest_created_at(cJSON **events, int count, long *out_newest) {
long newest = 0;
int found = 0;
for (int i = 0; i < count; i++) {
cJSON *ca = cJSON_GetObjectItem(events[i], "created_at");
if (!ca || !cJSON_IsNumber(ca)) continue;
long ts = (long)ca->valuedouble;
if (!found || ts > newest) {
newest = ts;
found = 1;
}
}
if (found && out_newest) *out_newest = newest;
return found;
}
/* Build the kinds array for a given pubkey (admin vs regular).
* Returns NULL if no kinds filter should be applied (admin_all_kinds). */
static cJSON *build_kinds(cr_config_t *cfg, const char *pk) {
int is_admin = cr_follow_is_root(cfg, pk);
if (is_admin && cfg->admin_all_kinds) {
return NULL;
}
cJSON *kinds = cJSON_CreateArray();
if (is_admin && cfg->admin_kind_count > 0) {
for (int i = 0; i < cfg->admin_kind_count; i++)
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(cfg->admin_kinds[i]));
} else {
for (int i = 0; i < cfg->kind_count; i++)
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(cfg->kinds[i]));
}
return kinds;
}
/* Resolve the relay URL list to query for a given pubkey. Sets *out_urls and
* *out_n. Caller frees *out_urls (but not the strings, which alias internal
* storage). Returns 0 on success, -1 if no relays available. */
static int resolve_relays(nostr_relay_pool_t *upstream,
const cr_relay_map_t *relay_map,
const char *pk,
const char ***out_urls,
int *out_n) {
*out_urls = NULL;
*out_n = 0;
/* Collect outbox relays for this author (from kind-10002). */
int outbox_count = 0;
const cr_outbox_entry_t *oe = NULL;
if (relay_map) {
oe = cr_relay_map_get_outbox(relay_map, pk);
if (oe) outbox_count = oe->relay_count;
}
/* Also collect all upstream (bootstrap) relays. We query BOTH the
* author's outbox relays AND the bootstrap relays to maximize coverage.
* Different relays may have different subsets of the author's events. */
char **listed = NULL;
nostr_pool_relay_status_t *statuses = NULL;
int upstream_count = nostr_relay_pool_list_relays(upstream, &listed, &statuses);
/* Merge outbox + upstream, deduplicating by URL. */
int max_total = outbox_count + upstream_count;
if (max_total <= 0) {
free(listed);
free(statuses);
return 0;
}
const char **urls = malloc(max_total * sizeof(char *));
if (!urls) { free(listed); free(statuses); return -1; }
int n = 0;
/* Add outbox relays first. */
if (oe) {
for (int j = 0; j < oe->relay_count && n < max_total; j++) {
urls[n++] = oe->relays[j];
}
}
/* Add upstream relays, skipping duplicates. */
for (int j = 0; j < upstream_count && n < max_total; j++) {
int dup = 0;
for (int k = 0; k < n; k++) {
if (strcmp(urls[k], listed[j]) == 0) { dup = 1; break; }
}
if (!dup) {
urls[n++] = listed[j];
}
}
free(listed);
free(statuses);
*out_urls = urls;
*out_n = n;
return 0;
}
/* ------------------------------------------------------------------ */
/* Init */
/* ------------------------------------------------------------------ */
void cr_backfill_init(cr_backfill_t *bf, cr_config_t *cfg) {
(void)cfg;
memset(bf, 0, sizeof(*bf));
g_bf = bf; /* Set singleton for status accessors */
/* In PG mode, check whether any incomplete authors remain in the DB.
* If so, backfill is in progress; otherwise we're in steady state.
* Outside PG mode there is no per-author table, so we just start. */
if (pg_mode_active()) {
int complete = 0, total = 0;
if (pg_inbox_count_backfill_progress(&complete, &total) == 0) {
bf->in_progress = (total > 0 && complete < total) ? 1 : 0;
DEBUG_INFO("backfill: init complete=%d/%d -> in_progress=%d",
complete, total, bf->in_progress);
return;
}
/* Count query failed - assume in progress so we keep trying. */
bf->in_progress = 1;
DEBUG_WARN("backfill: init could not read progress counts, "
"defaulting to in_progress=1");
return;
}
/* Legacy (non-PG) mode: no per-author table; just start. */
bf->in_progress = 1;
DEBUG_INFO("backfill: init (legacy mode) in_progress=1");
}
/* ------------------------------------------------------------------ */
/* Tick */
/* ------------------------------------------------------------------ */
int cr_backfill_tick(cr_backfill_t *bf, cr_config_t *cfg,
nostr_relay_pool_t *upstream,
cr_pubkey_set_t *followed,
cr_sink_t *sink, const cr_relay_map_t *relay_map) {
(void)followed; /* followed set is tracked in the DB now */
(void)relay_map; /* relays are tracked per-author in the DB */
if (!cfg->backfill.enabled) return -2;
if (!bf->in_progress) return -2;
/* Throttle: one query per tick_interval. */
time_t now = time(NULL);
if (bf->last_tick && (now - bf->last_tick) < cfg->backfill.tick_interval_seconds)
return 0;
/* Pick the next author that has at least one incomplete relay progress
* row (round-robin). */
char pk[CR_HEX_LEN];
if (pg_inbox_pick_next_author_with_incomplete_relays(pk, sizeof(pk),
&bf->author_round_cursor) != 0) {
/* No incomplete authors - steady state. */
bf->in_progress = 0;
DEBUG_INFO("backfill: no authors with incomplete relays, steady-state");
return -2;
}
int page_size = cfg->backfill.events_per_tick;
if (page_size < 1) page_size = 500;
bf->last_tick = now;
/* Get the incomplete relays for this author. */
cJSON *relays = pg_inbox_get_incomplete_relays(pk);
if (!relays) {
DEBUG_WARN("backfill: no incomplete relays returned for %s", pk);
/* Author has no relay rows - mark complete so we don't loop. */
pg_inbox_check_and_mark_author_complete(pk);
return 1;
}
int relay_count = cJSON_GetArraySize(relays);
DEBUG_TRACE("backfill: author %s has %d incomplete relay(s)", pk, relay_count);
cr_sink_set_source_class(sink, CR_SINK_CLASS_BACKFILL);
/* Query each incomplete relay one at a time. */
for (int r = 0; r < relay_count; r++) {
cJSON *entry = cJSON_GetArrayItem(relays, r);
if (!entry) continue;
cJSON *url_node = cJSON_GetObjectItem(entry, "relay_url");
cJSON *cur_node = cJSON_GetObjectItem(entry, "until_cursor");
if (!url_node || !cJSON_IsString(url_node)) continue;
const char *relay_url = cJSON_GetStringValue(url_node);
long until_cursor = (cur_node && cJSON_IsNumber(cur_node))
? (long)cur_node->valuedouble : 0;
if (until_cursor == 0) until_cursor = (long)now;
/* Build the filter for this single relay. */
cJSON *filter = cJSON_CreateObject();
cJSON *authors = cJSON_CreateArray();
cJSON_AddItemToArray(authors, cJSON_CreateString(pk));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON *kinds = build_kinds(cfg, pk);
if (kinds) cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(0.0));
cJSON_AddItemToObject(filter, "until", cJSON_CreateNumber((double)until_cursor));
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber((double)page_size));
DEBUG_TRACE("backfill: %s @ %s (until=%ld, limit=%d)",
pk, relay_url, until_cursor, page_size);
/* Record the active target for UI status display (both
* in-memory for local accessors and in PG for the relay). */
strncpy(bf->active_pubkey, pk, sizeof(bf->active_pubkey) - 1);
bf->active_pubkey[sizeof(bf->active_pubkey) - 1] = '\0';
strncpy(bf->active_relay, relay_url, sizeof(bf->active_relay) - 1);
bf->active_relay[sizeof(bf->active_relay) - 1] = '\0';
bf->active_relay_got_eose = 0;
pg_inbox_set_active_target(pk, relay_url);
/* Query this single relay using synchronous_query_relays_with_progress
* which gives us an EOSE/timeout callback — the relay's own signal
* of whether it sent everything or we timed out. */
backfill_query_ctx_t qctx = {0, 0};
const char *one_url = relay_url;
int ev_count = 0;
cJSON **events = synchronous_query_relays_with_progress(
&one_url, 1, filter, RELAY_QUERY_ALL_RESULTS,
&ev_count, 30, /* timeout in seconds */
backfill_query_callback, &qctx,
0, NULL /* nip42 disabled, no private key */);
cJSON_Delete(filter);
bf->active_relay_got_eose = qctx.got_eose;
if (!events || ev_count == 0) {
/* No events from this relay - it is drained for this author.
* (ev_count == 0 with EOSE means truly empty; without EOSE it
* means a connection failure — keep incomplete to retry.
* The pg_inbox_update_relay_progress SQL will auto-mark
* complete once consecutive_errors reaches 3.) */
int mark_complete = qctx.got_eose ? 1 : 0;
pg_inbox_update_relay_progress(pk, relay_url, until_cursor,
mark_complete, 0, qctx.last_status);
free(events);
if (!qctx.got_eose) {
/* Error or timeout with no events — surface the
* descriptive status at WARN so operators can see why. */
DEBUG_WARN("backfill: %s @ %s failed: %s (will auto-complete after 3 consecutive errors)",
pk, relay_url, qctx.last_status);
} else {
DEBUG_LOG("backfill: %s @ %s complete (no events, eose)",
pk, relay_url);
}
continue;
}
/* Find oldest and newest timestamps, then publish every event. */
long oldest_ts = 0;
long newest_ts = 0;
find_oldest_created_at(events, ev_count, &oldest_ts);
find_newest_created_at(events, ev_count, &newest_ts);
for (int k = 0; k < ev_count; k++) {
cr_sink_publish(sink, events[k]);
cJSON_Delete(events[k]);
}
free(events);
bf->events_total += ev_count;
/* Update last_event_at for forward catch-up gap tracking. */
if (newest_ts > 0) {
pg_inbox_update_last_event_at(pk, newest_ts);
}
DEBUG_LOG("backfill: %s @ %s -> %d events (until=%ld, oldest=%ld, eose=%d)",
pk, relay_url, ev_count, until_cursor, oldest_ts, qctx.got_eose);
/* Completion decision based on EOSE status:
* - ev_count >= page_size: saturated, advance cursor, keep incomplete
* - ev_count < page_size AND got EOSE: relay is truly drained
* - ev_count < page_size AND no EOSE (timeout): partial result,
* advance cursor but keep incomplete to retry next tick
* - Guard: if cursor didn't advance (oldest_ts >= until_cursor),
* mark complete to avoid infinite loop on identical timestamps. */
long next_cursor = (oldest_ts > 0) ? oldest_ts - 1 : 0;
int cursor_advanced = (oldest_ts > 0 && oldest_ts < until_cursor);
if (ev_count >= page_size) {
pg_inbox_update_relay_progress(pk, relay_url, next_cursor, 0, ev_count, qctx.last_status);
} else if (qctx.got_eose) {
/* Relay sent EOSE with < page_size events — truly drained. */
pg_inbox_update_relay_progress(pk, relay_url, next_cursor, 1, ev_count, qctx.last_status);
} else {
/* Timed out before EOSE — partial result. Keep incomplete
* to retry, unless the cursor didn't advance (stall guard).
* Timeouts DO count toward the consecutive_errors limit
* (a relay that consistently times out is effectively dead
* for backfill purposes), so the SQL will auto-mark
* complete after 3 consecutive timeouts/errors. */
int mark_complete = cursor_advanced ? 0 : 1;
pg_inbox_update_relay_progress(pk, relay_url, next_cursor,
mark_complete, ev_count, qctx.last_status);
DEBUG_WARN("backfill: %s @ %s partial: %s (%d events, will auto-complete after 3 consecutive timeouts/errors)",
pk, relay_url, qctx.last_status, ev_count);
}
}
/* Clear active target — done with this author. */
bf->active_relay[0] = '\0';
pg_inbox_set_active_target(pk, "");
cJSON_Delete(relays);
/* After querying all incomplete relays for this author, check whether
* the author is now fully complete. */
int crc = pg_inbox_check_and_mark_author_complete(pk);
if (crc == 1) {
DEBUG_LOG("backfill: author %s fully complete (all relays drained)", pk);
}
return 1;
}
+58
View File
@@ -0,0 +1,58 @@
/*
* caching_relay - per-author until-cursor drain backfill.
*
* Each followed author has a single persistent cursor (starting at `now`)
* stored in the caching_followed_pubkeys table. Each backfill tick picks
* the next incomplete author (round-robin), queries
* since=0 & until=cursor & limit=page_size
* publishes the page, and sets cursor = oldest_event_created_at - 1.
* Repeat until a page returns fewer than page_size events (beginning of
* the author's history reached), then mark the author complete.
*/
#ifndef CACHING_RELAY_BACKFILL_H
#define CACHING_RELAY_BACKFILL_H
#include "config.h"
#include "state.h"
#include "relay_sink.h"
#include "relay_discovery.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
typedef struct {
int in_progress; /* 1 if any incomplete authors remain */
long events_total; /* events pulled across all ticks (diagnostic) */
time_t last_tick; /* last time an author was queried (throttle) */
int author_round_cursor;/* round-robin cursor for author selection */
/* Current backfill target (for UI status display). */
char active_pubkey[65]; /* pubkey currently being backfilled (or "") */
char active_relay[256]; /* relay URL currently being queried (or "") */
int active_relay_got_eose; /* 1 if last query got EOSE, 0 if timed out */
} cr_backfill_t;
/* Global access to the current backfill target for status reporting.
* Returns pointers to static storage in the singleton cr_backfill_t.
* Safe to call from the main loop; not thread-safe but backfill is
* single-threaded. */
const char *cr_backfill_active_pubkey(void);
const char *cr_backfill_active_relay(void);
int cr_backfill_active_got_eose(void);
/* Initialize backfill state. Checks the caching_followed_pubkeys table for
* any incomplete authors; sets in_progress accordingly. */
void cr_backfill_init(cr_backfill_t *bf, cr_config_t *cfg);
/* Perform one backfill tick. Picks the next incomplete author from the DB
* (round-robin), queries one page of history, publishes it to the sink, and
* updates the per-author cursor in the DB.
*
* Returns:
* 1 if a tick was performed (an author was queried)
* 0 if throttled (tick_interval not elapsed) - caller should pump pools
* -2 if backfill is complete (no incomplete authors) - caller goes steady-state
*/
int cr_backfill_tick(cr_backfill_t *bf, cr_config_t *cfg,
nostr_relay_pool_t *upstream,
cr_pubkey_set_t *followed,
cr_sink_t *sink, const cr_relay_map_t *relay_map);
#endif /* CACHING_RELAY_BACKFILL_H */
+774
View File
@@ -0,0 +1,774 @@
/*
* cache_all_test - feasibility test for "cache everything" approach.
*
* Connects to configured relays, subscribes to ALL events (no filter),
* logs verbatim relay responses, tracks kind distribution and event sizes.
*
* Usage:
* ./build/cache_all_test -c cache_all_test_config.jsonc -t 300 -d 3
*
* Output files:
* cache_all_test_raw_relay_<ts>.log - verbatim relay responses
* cache_all_test_report_<ts>.txt - summary report
* cache_all_test_stats_<ts>.json - machine-readable stats
*/
#define _GNU_SOURCE
#include "cache_all_test.h"
#include "config.h"
#include "state.h"
#include "debug.h"
#include "main.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nostr_log.h"
#include "../nostr_core_lib/cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
/* ------------------------------------------------------------------ */
/* Forward nostr_core_lib logging */
/* ------------------------------------------------------------------ */
static void nostr_log_forwarder(int level, const char *component,
const char *message, void *user_data) {
(void)user_data;
if (level >= 5) {
DEBUG_TRACE("[nostr:%s] %s", component ? component : "?", message ? message : "");
} else if (level >= 4) {
DEBUG_LOG("[nostr:%s] %s", component ? component : "?", message ? message : "");
} else if (level >= 3) {
DEBUG_INFO("[nostr:%s] %s", component ? component : "?", message ? message : "");
} else if (level >= 2) {
DEBUG_WARN("[nostr:%s] %s", component ? component : "?", message ? message : "");
} else {
DEBUG_ERROR("[nostr:%s] %s", component ? component : "?", message ? message : "");
}
}
/* ------------------------------------------------------------------ */
/* Shutdown flag */
/* ------------------------------------------------------------------ */
static volatile sig_atomic_t g_shutdown = 0;
static void on_signal(int sig) {
if (sig == SIGINT || sig == SIGTERM) g_shutdown = 1;
}
/* ------------------------------------------------------------------ */
/* Per-relay stats */
/* ------------------------------------------------------------------ */
#define MAX_RELAYS 64
#define MAX_KIND_TRACK 256
typedef struct {
char url[256];
long events_received;
long eose_count;
long closed_count;
long notice_count;
long error_count;
long disconnect_count;
long reconnect_count;
int is_connected;
} relay_stats_t;
typedef struct {
int kind;
long count;
long total_bytes;
long max_bytes;
} kind_stat_t;
/* ------------------------------------------------------------------ */
/* Global test state */
/* ------------------------------------------------------------------ */
typedef struct {
relay_stats_t relays[MAX_RELAYS];
int relay_count;
kind_stat_t kinds[MAX_KIND_TRACK];
int kind_count;
long total_events;
long total_bytes;
long max_event_bytes;
int max_event_kind;
char pubkeys_seen[65536][65];
int pubkey_count;
FILE *raw_log;
char raw_log_path[1024];
time_t start_time;
long duration_seconds;
time_t last_summary_log;
cr_seen_ring_t seen;
} test_state_t;
static test_state_t g_state;
/* ------------------------------------------------------------------ */
/* Stats helpers */
/* ------------------------------------------------------------------ */
static int find_or_add_relay(const char *url) {
for (int i = 0; i < g_state.relay_count; i++) {
if (strcmp(g_state.relays[i].url, url) == 0) return i;
}
if (g_state.relay_count >= MAX_RELAYS) return -1;
int idx = g_state.relay_count++;
strncpy(g_state.relays[idx].url, url, sizeof(g_state.relays[idx].url) - 1);
return idx;
}
static int find_or_add_kind(int kind) {
for (int i = 0; i < g_state.kind_count; i++) {
if (g_state.kinds[i].kind == kind) return i;
}
if (g_state.kind_count >= MAX_KIND_TRACK) return -1;
int idx = g_state.kind_count++;
g_state.kinds[idx].kind = kind;
g_state.kinds[idx].count = 0;
g_state.kinds[idx].total_bytes = 0;
g_state.kinds[idx].max_bytes = 0;
return idx;
}
static int find_or_add_pubkey(const char *hex) {
for (int i = 0; i < g_state.pubkey_count; i++) {
if (strcmp(g_state.pubkeys_seen[i], hex) == 0) return i;
}
if (g_state.pubkey_count >= 65536) return -1;
strncpy(g_state.pubkeys_seen[g_state.pubkey_count], hex, 64);
g_state.pubkeys_seen[g_state.pubkey_count][64] = '\0';
return g_state.pubkey_count++;
}
/* ------------------------------------------------------------------ */
/* Raw relay log writing */
/* ------------------------------------------------------------------ */
static void write_raw_log(const char *relay_url, const char *fmt, ...) {
if (!g_state.raw_log) return;
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char ts[32];
strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(g_state.raw_log, "[%s] [RELAY] %s ", ts, relay_url ? relay_url : "?");
va_list args;
va_start(args, fmt);
vfprintf(g_state.raw_log, fmt, args);
va_end(args);
fprintf(g_state.raw_log, "\n");
fflush(g_state.raw_log);
}
/* ------------------------------------------------------------------ */
/* Subscription callbacks */
/* ------------------------------------------------------------------ */
static void on_event(cJSON *event, const char *relay_url, void *user_data) {
(void)user_data;
if (!event) return;
cJSON *id = cJSON_GetObjectItem(event, "id");
cJSON *kind = cJSON_GetObjectItem(event, "kind");
cJSON *pubkey = cJSON_GetObjectItem(event, "pubkey");
if (!id || !cJSON_IsString(id)) return;
if (!kind || !cJSON_IsNumber(kind)) return;
if (!pubkey || !cJSON_IsString(pubkey)) return;
const char *eid = cJSON_GetStringValue(id);
int kind_num = (int)cJSON_GetNumberValue(kind);
const char *pk = cJSON_GetStringValue(pubkey);
/* Dedup via seen ring. */
if (cr_seen_ring_add(&g_state.seen, eid) == 0) return;
/* Calculate JSON size. */
char *json_str = cJSON_PrintUnformatted(event);
long ev_size = json_str ? (long)strlen(json_str) : 0;
free(json_str);
/* Update relay stats. */
int ridx = relay_url ? find_or_add_relay(relay_url) : -1;
if (ridx >= 0) g_state.relays[ridx].events_received++;
/* Update kind stats. */
int kidx = find_or_add_kind(kind_num);
if (kidx >= 0) {
g_state.kinds[kidx].count++;
g_state.kinds[kidx].total_bytes += ev_size;
if (ev_size > g_state.kinds[kidx].max_bytes)
g_state.kinds[kidx].max_bytes = ev_size;
}
/* Update global stats. */
g_state.total_events++;
g_state.total_bytes += ev_size;
if (ev_size > g_state.max_event_bytes) {
g_state.max_event_bytes = ev_size;
g_state.max_event_kind = kind_num;
}
find_or_add_pubkey(pk);
/* Periodic summary log (every 1000 events). */
if (g_state.total_events % 1000 == 0) {
time_t now = time(NULL);
long elapsed = (long)(now - g_state.start_time);
double rate = elapsed > 0 ? (double)g_state.total_events / elapsed : 0;
DEBUG_INFO("[STATS] %ld events in %lds (%.1f ev/s) | "
"%d relays | %d kinds | %d pubkeys | %.1f MB",
g_state.total_events, elapsed, rate,
g_state.relay_count, g_state.kind_count,
g_state.pubkey_count,
(double)g_state.total_bytes / (1024.0 * 1024.0));
}
}
static void on_eose(cJSON **events, int event_count, void *user_data) {
(void)events; (void)event_count; (void)user_data;
/* EOSE doesn't carry a relay URL in the callback, so we just log it. */
DEBUG_INFO("[RELAY] EOSE");
write_raw_log("?", "EOSE");
}
/* ------------------------------------------------------------------ */
/* Report generation */
/* ------------------------------------------------------------------ */
static int kind_name(int kind, char *buf, size_t bufsz) {
switch (kind) {
case 0: snprintf(buf, bufsz, "Profile metadata"); return 1;
case 1: snprintf(buf, bufsz, "Text note"); return 1;
case 3: snprintf(buf, bufsz, "Follow list"); return 1;
case 4: snprintf(buf, bufsz, "Encrypted DM"); return 1;
case 5: snprintf(buf, bufsz, "Deletion"); return 1;
case 6: snprintf(buf, bufsz, "Repost"); return 1;
case 7: snprintf(buf, bufsz, "Reaction"); return 1;
case 13: snprintf(buf, bufsz, "Seal (NIP-59)"); return 1;
case 16: snprintf(buf, bufsz, "Generic repost"); return 1;
case 20: snprintf(buf, bufsz, "Picture"); return 1;
case 21: snprintf(buf, bufsz, "Video"); return 1;
case 445: snprintf(buf, bufsz, "Encrypted group msg"); return 1;
case 1059: snprintf(buf, bufsz, "Gift wrap seal"); return 1;
case 1063: snprintf(buf, bufsz, "File metadata"); return 1;
case 1111: snprintf(buf, bufsz, "Comment"); return 1;
case 1984: snprintf(buf, bufsz, "Report"); return 1;
case 9734: snprintf(buf, bufsz, "Zap request"); return 1;
case 9735: snprintf(buf, bufsz, "Zap receipt"); return 1;
case 10000: snprintf(buf, bufsz, "Mute list"); return 1;
case 10001: snprintf(buf, bufsz, "Pinned notes"); return 1;
case 10002: snprintf(buf, bufsz, "Relay list"); return 1;
case 10003: snprintf(buf, bufsz, "Bookmarks"); return 1;
case 10050: snprintf(buf, bufsz, "DM relays"); return 1;
case 13194: snprintf(buf, bufsz, "NWC info"); return 1;
case 21059: snprintf(buf, bufsz, "Gift wrap (NIP-59)"); return 1;
case 23194: snprintf(buf, bufsz, "NWC request"); return 1;
case 23195: snprintf(buf, bufsz, "NWC response"); return 1;
case 30023: snprintf(buf, bufsz, "Long-form article"); return 1;
case 30078: snprintf(buf, bufsz, "App data"); return 1;
case 30089: snprintf(buf, bufsz, "Chunked data"); return 1;
case 30315: snprintf(buf, bufsz, "User status"); return 1;
case 31989: snprintf(buf, bufsz, "App handler rec"); return 1;
case 31990: snprintf(buf, bufsz, "App handler info"); return 1;
case 34550: snprintf(buf, bufsz, "Community def"); return 1;
default:
if (kind >= 20000 && kind < 30000)
snprintf(buf, bufsz, "Ephemeral");
else if (kind >= 30000 && kind < 40000)
snprintf(buf, bufsz, "Addressable");
else
snprintf(buf, bufsz, "Unknown");
return 0;
}
}
static int write_report(void) {
char path[1024];
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char ts[32];
strftime(ts, sizeof(ts), "%Y%m%d_%H%M%S", tm_info);
snprintf(path, sizeof(path), "cache_all_test_report_%s.txt", ts);
FILE *f = fopen(path, "w");
if (!f) { DEBUG_ERROR("failed to write report"); return -1; }
long elapsed = (long)(now - g_state.start_time);
int hours = (int)(elapsed / 3600);
int mins = (int)((elapsed % 3600) / 60);
int secs = (int)(elapsed % 60);
fprintf(f, "=== Cache-All Feasibility Test Report ===\n");
fprintf(f, "Duration: %dh %dm %ds\n", hours, mins, secs);
fprintf(f, "\n=== Global Summary ===\n");
fprintf(f, "Total events: %ld\n", g_state.total_events);
fprintf(f, "Total bytes: %ld (%.2f MB)\n",
g_state.total_bytes, (double)g_state.total_bytes / (1024.0 * 1024.0));
fprintf(f, "Avg event size: %.0f bytes\n",
g_state.total_events > 0
? (double)g_state.total_bytes / g_state.total_events : 0);
fprintf(f, "Max event: %ld bytes (kind %d)\n",
g_state.max_event_bytes, g_state.max_event_kind);
fprintf(f, "Unique pubkeys: %d\n", g_state.pubkey_count);
fprintf(f, "Relays: %d\n\n", g_state.relay_count);
fprintf(f, "=== Relay Summary ===\n");
fprintf(f, "%-48s %8s %6s %6s %6s %6s %6s %s\n",
"Relay", "Events", "EOSE", "CLOSED", "NOTICE", "Err", "Disc", "Status");
for (int i = 0; i < g_state.relay_count; i++) {
relay_stats_t *r = &g_state.relays[i];
fprintf(f, "%-48s %8ld %6ld %6ld %6ld %6ld %6ld %s\n",
r->url, r->events_received, r->eose_count,
r->closed_count, r->notice_count, r->error_count,
r->disconnect_count,
r->is_connected ? "OK" : "DOWN");
}
fprintf(f, "\n");
fprintf(f, "=== Kind Distribution ===\n");
fprintf(f, "%-8s %-30s %8s %6s %14s %10s %10s\n",
"Kind", "Name", "Count", "%", "Total Bytes", "Avg", "Max");
/* Sort by count descending. */
for (int i = 0; i < g_state.kind_count; i++) {
for (int j = i + 1; j < g_state.kind_count; j++) {
if (g_state.kinds[j].count > g_state.kinds[i].count) {
kind_stat_t tmp = g_state.kinds[i];
g_state.kinds[i] = g_state.kinds[j];
g_state.kinds[j] = tmp;
}
}
}
for (int i = 0; i < g_state.kind_count; i++) {
kind_stat_t *k = &g_state.kinds[i];
double pct = g_state.total_events > 0
? (double)k->count / g_state.total_events * 100.0 : 0;
char name[64];
kind_name(k->kind, name, sizeof(name));
fprintf(f, "%-8d %-30s %8ld %5.1f%% %12ld %8.0f %8ld\n",
k->kind, name, k->count, pct,
k->total_bytes,
k->count > 0 ? (double)k->total_bytes / k->count : 0,
k->max_bytes);
}
fprintf(f, "\n");
fprintf(f, "=== Raw Relay Response Log ===\n");
if (g_state.raw_log) {
fflush(g_state.raw_log);
FILE *rl = fopen(g_state.raw_log_path, "r");
if (rl) {
char line[1024];
while (fgets(line, sizeof(line), rl)) fputs(line, f);
fclose(rl);
}
}
fprintf(f, "\n");
fclose(f);
DEBUG_INFO("report: %s", path);
return 0;
}
static int write_json_stats(void) {
char path[1024];
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char ts[32];
strftime(ts, sizeof(ts), "%Y%m%d_%H%M%S", tm_info);
snprintf(path, sizeof(path), "cache_all_test_stats_%s.json", ts);
cJSON *root = cJSON_CreateObject();
if (!root) return -1;
long elapsed = (long)(now - g_state.start_time);
cJSON_AddNumberToObject(root, "duration_seconds", elapsed);
cJSON_AddNumberToObject(root, "total_events", g_state.total_events);
cJSON_AddNumberToObject(root, "total_bytes", g_state.total_bytes);
cJSON_AddNumberToObject(root, "unique_pubkeys", g_state.pubkey_count);
cJSON_AddNumberToObject(root, "relay_count", g_state.relay_count);
cJSON *relays = cJSON_CreateArray();
for (int i = 0; i < g_state.relay_count; i++) {
relay_stats_t *r = &g_state.relays[i];
cJSON *rj = cJSON_CreateObject();
cJSON_AddStringToObject(rj, "url", r->url);
cJSON_AddNumberToObject(rj, "events", r->events_received);
cJSON_AddNumberToObject(rj, "eose", r->eose_count);
cJSON_AddNumberToObject(rj, "closed", r->closed_count);
cJSON_AddNumberToObject(rj, "notice", r->notice_count);
cJSON_AddNumberToObject(rj, "errors", r->error_count);
cJSON_AddNumberToObject(rj, "disconnects", r->disconnect_count);
cJSON_AddBoolToObject(rj, "connected", r->is_connected);
cJSON_AddItemToArray(relays, rj);
}
cJSON_AddItemToObject(root, "relays", relays);
cJSON *kinds = cJSON_CreateArray();
for (int i = 0; i < g_state.kind_count; i++) {
kind_stat_t *k = &g_state.kinds[i];
cJSON *kj = cJSON_CreateObject();
cJSON_AddNumberToObject(kj, "kind", k->kind);
cJSON_AddNumberToObject(kj, "count", k->count);
cJSON_AddNumberToObject(kj, "total_bytes", k->total_bytes);
cJSON_AddNumberToObject(kj, "max_bytes", k->max_bytes);
cJSON_AddItemToArray(kinds, kj);
}
cJSON_AddItemToObject(root, "kinds", kinds);
char *json = cJSON_Print(root);
cJSON_Delete(root);
if (!json) return -1;
FILE *f = fopen(path, "w");
if (!f) { free(json); return -1; }
fprintf(f, "%s\n", json);
fclose(f);
free(json);
DEBUG_INFO("stats: %s", path);
return 0;
}
/* ------------------------------------------------------------------ */
/* Main entry point */
/* ------------------------------------------------------------------ */
int run_cache_all_test(const char *config_path,
long duration_seconds,
int log_level) {
memset(&g_state, 0, sizeof(g_state));
g_state.duration_seconds = duration_seconds;
cr_seen_ring_init(&g_state.seen);
/* Open raw relay log. */
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char ts[32];
strftime(ts, sizeof(ts), "%Y%m%d_%H%M%S", tm_info);
snprintf(g_state.raw_log_path, sizeof(g_state.raw_log_path),
"cache_all_test_raw_relay_%s.log", ts);
g_state.raw_log = fopen(g_state.raw_log_path, "w");
if (!g_state.raw_log) {
fprintf(stderr, "ERROR: cannot create raw log '%s': %s\n",
g_state.raw_log_path, strerror(errno));
return 1;
}
fprintf(g_state.raw_log, "# Cache-All Feasibility Test - Raw Relay Log\n");
fprintf(g_state.raw_log, "# Started: %s", ctime(&now));
fprintf(g_state.raw_log, "# Config: %s\n", config_path ? config_path : "(none)");
fprintf(g_state.raw_log, "# Duration: %ld seconds\n", duration_seconds);
fflush(g_state.raw_log);
debug_init(log_level);
DEBUG_INFO("cache_all_test starting (config=%s, duration=%lds, loglevel=%d)",
config_path ? config_path : "(none)", duration_seconds, log_level);
nostr_set_log_callback(nostr_log_forwarder, NULL);
nostr_set_log_level((nostr_log_level_t)log_level);
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_signal;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
signal(SIGPIPE, SIG_IGN);
if (nostr_crypto_init() != NOSTR_SUCCESS) {
fprintf(stderr, "ERROR: nostr_crypto_init failed\n");
fclose(g_state.raw_log);
return 1;
}
/* Load config. */
cr_config_t cfg;
if (cr_config_load(&cfg, config_path) != 0) {
fprintf(stderr, "ERROR: failed to load config\n");
fclose(g_state.raw_log);
nostr_crypto_cleanup();
return 1;
}
/* Create upstream pool with custom reconnect config.
* Use 25s ping interval to stay ahead of Primal's 30s idle timeout. */
nostr_pool_reconnect_config_t reconnect_cfg = {
.enable_auto_reconnect = 1,
.max_reconnect_attempts = 10,
.initial_reconnect_delay_ms = 1000,
.max_reconnect_delay_ms = 30000,
.reconnect_backoff_multiplier = 2,
.reconnect_reset_stability_seconds = 30,
.ping_interval_seconds = 25,
.pong_timeout_seconds = 10
};
nostr_relay_pool_t *upstream = nostr_relay_pool_create(&reconnect_cfg);
if (!upstream) {
fprintf(stderr, "ERROR: failed to create pool\n");
fclose(g_state.raw_log);
cr_config_free(&cfg);
nostr_crypto_cleanup();
return 1;
}
for (int i = 0; i < cfg.upstream_count; i++) {
if (nostr_relay_pool_add_relay(upstream, cfg.upstream_relays[i])
== NOSTR_SUCCESS) {
DEBUG_INFO("added relay: %s", cfg.upstream_relays[i]);
find_or_add_relay(cfg.upstream_relays[i]);
}
}
/* Wait for connections (up to 15s). */
DEBUG_INFO("waiting for connections...");
for (int i = 0; i < 150 && !g_shutdown; i++) {
nostr_relay_pool_run(upstream, 100);
if (i % 50 == 0 && i > 0) {
int connected = 0;
for (int r = 0; r < g_state.relay_count; r++) {
nostr_pool_relay_status_t st =
nostr_relay_pool_get_relay_status(upstream,
g_state.relays[r].url);
g_state.relays[r].is_connected = (st == NOSTR_POOL_RELAY_CONNECTED);
if (g_state.relays[r].is_connected) connected++;
}
DEBUG_INFO(" %d/%d relays connected after %dms",
connected, g_state.relay_count, (i+1)*100);
}
}
/* Log final connection status. */
for (int r = 0; r < g_state.relay_count; r++) {
nostr_pool_relay_status_t st =
nostr_relay_pool_get_relay_status(upstream, g_state.relays[r].url);
g_state.relays[r].is_connected = (st == NOSTR_POOL_RELAY_CONNECTED);
const char *status_str = "?";
if (st == NOSTR_POOL_RELAY_CONNECTED) status_str = "connected";
else if (st == NOSTR_POOL_RELAY_CONNECTING) status_str = "connecting";
else if (st == NOSTR_POOL_RELAY_DISCONNECTED) status_str = "disconnected";
DEBUG_INFO(" %s: %s", g_state.relays[r].url, status_str);
write_raw_log(g_state.relays[r].url, "STATUS: %s", status_str);
}
/* Open subscription: ALL events (no filter). */
cJSON *filter = cJSON_CreateObject();
cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber((double)time(NULL)));
char **listed = NULL;
nostr_pool_relay_status_t *statuses = NULL;
int n = nostr_relay_pool_list_relays(upstream, &listed, &statuses);
const char **urls = NULL;
if (n > 0) {
urls = malloc(n * sizeof(char *));
for (int j = 0; j < n; j++) urls[j] = listed[j];
}
nostr_pool_subscription_t *sub = NULL;
if (n > 0 && urls) {
sub = nostr_relay_pool_subscribe(
upstream, urls, n, filter,
on_event, on_eose, NULL,
0, 1, NOSTR_POOL_EOSE_FULL_SET, 0, 0);
}
free(urls);
free(listed);
free(statuses);
cJSON_Delete(filter);
if (!sub) {
DEBUG_ERROR("failed to open subscription");
} else {
DEBUG_INFO("subscription opened on %d relays", n);
}
/* Main loop. */
g_state.start_time = time(NULL);
g_state.last_summary_log = g_state.start_time;
time_t last_resubscribe = g_state.start_time;
DEBUG_INFO("entering main loop for %ld seconds", duration_seconds);
while (!g_shutdown) {
if (duration_seconds > 0) {
long elapsed = (long)(time(NULL) - g_state.start_time);
if (elapsed >= duration_seconds) {
DEBUG_INFO("duration reached (%lds)", elapsed);
break;
}
}
nostr_relay_pool_run(upstream, 100);
/* Poll connection status. */
for (int r = 0; r < g_state.relay_count; r++) {
nostr_pool_relay_status_t st =
nostr_relay_pool_get_relay_status(upstream, g_state.relays[r].url);
int was = g_state.relays[r].is_connected;
int now = (st == NOSTR_POOL_RELAY_CONNECTED);
g_state.relays[r].is_connected = now;
if (now && !was) {
g_state.relays[r].reconnect_count++;
DEBUG_INFO("[RELAY] %s CONNECTED", g_state.relays[r].url);
write_raw_log(g_state.relays[r].url, "CONNECTED");
} else if (!now && was) {
g_state.relays[r].disconnect_count++;
const char *err = nostr_relay_pool_get_relay_last_connection_error(
upstream, g_state.relays[r].url);
DEBUG_WARN("[RELAY] %s DISCONNECTED: %s",
g_state.relays[r].url, err ? err : "");
write_raw_log(g_state.relays[r].url, "DISCONNECTED: %s", err ? err : "");
}
}
/* Periodic resubscribe (every 5 min) to re-establish REQ on reconnected relays. */
time_t now_t = time(NULL);
if ((now_t - last_resubscribe) >= 300) {
if (sub) {
nostr_pool_subscription_close(sub);
sub = NULL;
}
/* Re-open subscription with fresh filter. */
cJSON *new_filter = cJSON_CreateObject();
cJSON_AddItemToObject(new_filter, "since",
cJSON_CreateNumber((double)time(NULL)));
char **rel_list = NULL;
nostr_pool_relay_status_t *rel_statuses = NULL;
int rel_n = nostr_relay_pool_list_relays(upstream, &rel_list, &rel_statuses);
const char **rel_urls = NULL;
if (rel_n > 0) {
rel_urls = malloc(rel_n * sizeof(char *));
for (int j = 0; j < rel_n; j++) rel_urls[j] = rel_list[j];
}
if (rel_n > 0 && rel_urls) {
sub = nostr_relay_pool_subscribe(
upstream, rel_urls, rel_n, new_filter,
on_event, on_eose, NULL,
0, 1, NOSTR_POOL_EOSE_FULL_SET, 0, 0);
}
free(rel_urls);
free(rel_list);
free(rel_statuses);
cJSON_Delete(new_filter);
if (sub) {
DEBUG_INFO("resubscribed on %d relays", rel_n);
} else {
DEBUG_WARN("resubscribe failed");
}
last_resubscribe = now_t;
}
/* Periodic heartbeat with ping stats. */
if ((now_t - g_state.last_summary_log) >= 30) {
long elapsed = (long)(now_t - g_state.start_time);
double rate = elapsed > 0 ? (double)g_state.total_events / elapsed : 0;
int connected = 0;
for (int r = 0; r < g_state.relay_count; r++)
if (g_state.relays[r].is_connected) connected++;
/* Get ping stats for first connected relay. */
double ping = 0;
int ping_samples = 0;
for (int r = 0; r < g_state.relay_count && ping_samples == 0; r++) {
if (g_state.relays[r].is_connected) {
const nostr_relay_stats_t *stats =
nostr_relay_pool_get_relay_stats(upstream,
g_state.relays[r].url);
if (stats) {
ping = stats->ping_latency_current;
ping_samples = stats->ping_samples;
}
}
}
DEBUG_INFO("[HEARTBEAT] %ld events in %lds (%.1f ev/s) | "
"%d/%d relays | %d kinds | %d pubkeys | "
"ping=%.1fms(%d)",
g_state.total_events, elapsed, rate,
connected, g_state.relay_count,
g_state.kind_count, g_state.pubkey_count,
ping * 1000, ping_samples);
g_state.last_summary_log = now_t;
}
}
if (sub) nostr_pool_subscription_close(sub);
DEBUG_INFO("writing reports...");
write_report();
write_json_stats();
if (g_state.raw_log) {
fprintf(g_state.raw_log, "# Test ended: %s", ctime(&now));
fclose(g_state.raw_log);
DEBUG_INFO("raw log: %s", g_state.raw_log_path);
}
nostr_relay_pool_destroy(upstream);
cr_config_free(&cfg);
nostr_crypto_cleanup();
DEBUG_INFO("clean exit");
return 0;
}
/* ------------------------------------------------------------------ */
/* Standalone entry point */
/* ------------------------------------------------------------------ */
static void usage(const char *prog) {
fprintf(stderr,
"cache_all_test %s - feasibility test for cache-all approach\n"
"\n"
"Usage: %s -c <config.jsonc> -t <seconds> [options]\n"
"\n"
"Options:\n"
" -c, --config <file> Path to .jsonc config file (required)\n"
" -t, --time <seconds> Test duration in seconds (required)\n"
" -d, --debug <level> Log level 0-5 (default: 3)\n"
" -h, --help Show this help\n"
"\n"
"Output files:\n"
" cache_all_test_raw_relay_<ts>.log - verbatim relay responses\n"
" cache_all_test_report_<ts>.txt - summary report\n"
" cache_all_test_stats_<ts>.json - machine-readable stats\n",
CR_VERSION, prog);
}
int main(int argc, char **argv) {
const char *config_path = NULL;
long duration = 0;
int log_level = DEBUG_LEVEL_INFO;
static struct option longopts[] = {
{"config", required_argument, 0, 'c'},
{"time", required_argument, 0, 't'},
{"debug", required_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "c:t:d:h", longopts, NULL)) != -1) {
switch (opt) {
case 'c': config_path = optarg; break;
case 't': duration = atol(optarg); break;
case 'd': log_level = atoi(optarg); break;
case 'h': usage(argv[0]); return 0;
default: usage(argv[0]); return 1;
}
}
if (!config_path) {
fprintf(stderr, "ERROR: -c <config> is required\n\n");
usage(argv[0]);
return 1;
}
if (duration <= 0) {
fprintf(stderr, "ERROR: -t <seconds> is required and must be > 0\n\n");
usage(argv[0]);
return 1;
}
return run_cache_all_test(config_path, duration, log_level);
}
+26
View File
@@ -0,0 +1,26 @@
/*
* cache_all_test - feasibility test for "cache everything" approach.
*
* Standalone test program that connects to all outbox relays of followed
* pubkeys and subscribes to ALL events (no author filter) to determine
* if relays will tolerate this level of data flow.
*
* Logs raw relay responses verbatim. Tracks kind distribution, event sizes,
* per-relay stats. Generates summary report + raw relay log file.
*
* See plans/cache_all_feasibility_test_plan.md for full design.
*/
#ifndef CACHE_ALL_TEST_H
#define CACHE_ALL_TEST_H
/* Run the cache-all feasibility test.
* config_path: path to .jsonc config file
* duration_seconds: how long to run (0 = run until SIGINT)
* log_level: debug level 0-5
* Returns 0 on success, -1 on error.
*/
int run_cache_all_test(const char *config_path,
long duration_seconds,
int log_level);
#endif /* CACHE_ALL_TEST_H */
+290
View File
@@ -0,0 +1,290 @@
/*
* caching_relay - config parsing + persistent state (in the .jsonc file itself)
*/
#define _GNU_SOURCE
#include "config.h"
#include "jsonc_strip.h"
#include "debug.h"
#include "../nostr_core_lib/cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
static int read_file(const char *path, char **out, size_t *out_len) {
FILE *f = fopen(path, "rb");
if (!f) {
DEBUG_ERROR("cannot open config '%s': %s", path, strerror(errno));
return -1;
}
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
char *buf = malloc(sz + 1);
if (!buf) { fclose(f); return -1; }
size_t n = fread(buf, 1, sz, f);
fclose(f);
buf[n] = '\0';
*out = buf;
if (out_len) *out_len = n;
return 0;
}
static void copy_string_array(cJSON *arr, char *dst, int max, int *count_out,
int elem_len) {
int count = 0;
if (arr && cJSON_IsArray(arr)) {
cJSON *item;
cJSON_ArrayForEach(item, arr) {
if (count >= max) break;
if (!cJSON_IsString(item)) continue;
const char *s = cJSON_GetStringValue(item);
if (!s) continue;
strncpy(dst + count * elem_len, s, elem_len - 1);
dst[count * elem_len + (elem_len - 1)] = '\0';
count++;
}
}
*count_out = count;
}
static void copy_int_array(cJSON *arr, int *dst, int max, int *count_out) {
int count = 0;
if (arr && cJSON_IsArray(arr)) {
cJSON *item;
cJSON_ArrayForEach(item, arr) {
if (count >= max) break;
if (cJSON_IsNumber(item)) {
dst[count++] = (int)cJSON_GetNumberValue(item);
}
}
}
*count_out = count;
}
static void copy_long_array(cJSON *arr, long *dst, int max, int *count_out) {
int count = 0;
if (arr && cJSON_IsArray(arr)) {
cJSON *item;
cJSON_ArrayForEach(item, arr) {
if (count >= max) break;
if (cJSON_IsNumber(item)) {
dst[count++] = (long)cJSON_GetNumberValue(item);
}
}
}
*count_out = count;
}
int cr_config_load(cr_config_t *cfg, const char *path) {
memset(cfg, 0, sizeof(*cfg));
strncpy(cfg->path, path, sizeof(cfg->path) - 1);
char *raw = NULL;
size_t raw_len = 0;
if (read_file(path, &raw, &raw_len) != 0) return -1;
char *stripped = jsonc_strip_comments(raw, raw_len);
free(raw);
if (!stripped) {
DEBUG_ERROR("failed to strip comments from config");
return -1;
}
cJSON *root = cJSON_Parse(stripped);
free(stripped);
if (!root) {
DEBUG_ERROR("failed to parse config JSON");
return -1;
}
/* root_npubs */
copy_string_array(cJSON_GetObjectItem(root, "root_npubs"),
(char *)cfg->root_npubs, CR_MAX_ROOT_NPUBS,
&cfg->root_npub_count, CR_NPUB_LEN);
/* upstream_relays */
copy_string_array(cJSON_GetObjectItem(root, "upstream_relays"),
(char *)cfg->upstream_relays, CR_MAX_UPSTREAM,
&cfg->upstream_count, CR_URL_LEN);
/* local_relay */
cJSON *local = cJSON_GetObjectItem(root, "local_relay");
if (local && cJSON_IsString(local)) {
strncpy(cfg->local_relay, cJSON_GetStringValue(local), CR_URL_LEN - 1);
}
/* kinds */
copy_int_array(cJSON_GetObjectItem(root, "kinds"),
cfg->kinds, CR_MAX_KINDS, &cfg->kind_count);
/* admin_kinds - kinds to follow specifically for root (admin) npubs.
* Supports [*] to mean "all kinds" (no kind filter for admin). */
cJSON *ak = cJSON_GetObjectItem(root, "admin_kinds");
if (ak && cJSON_IsArray(ak)) {
cJSON *item;
cJSON_ArrayForEach(item, ak) {
if (cJSON_IsString(item)) {
const char *s = cJSON_GetStringValue(item);
if (s && strcmp(s, "*") == 0) {
cfg->admin_all_kinds = 1;
break;
}
}
}
if (!cfg->admin_all_kinds) {
copy_int_array(ak, cfg->admin_kinds, CR_MAX_KINDS, &cfg->admin_kind_count);
}
}
/* backfill */
cJSON *bf = cJSON_GetObjectItem(root, "backfill");
if (bf) {
cfg->backfill.enabled = cJSON_IsTrue(cJSON_GetObjectItem(bf, "enabled"));
cJSON *ept = cJSON_GetObjectItem(bf, "events_per_tick");
if (ept) cfg->backfill.events_per_tick = (int)cJSON_GetNumberValue(ept);
cJSON *tis = cJSON_GetObjectItem(bf, "tick_interval_seconds");
if (tis) cfg->backfill.tick_interval_seconds = (int)cJSON_GetNumberValue(tis);
}
/* live */
cJSON *lv = cJSON_GetObjectItem(root, "live");
if (lv) {
cfg->live.enabled = cJSON_IsTrue(cJSON_GetObjectItem(lv, "enabled"));
cJSON *rsi = cJSON_GetObjectItem(lv, "resubscribe_interval_seconds");
if (rsi) cfg->live.resubscribe_interval_seconds = (int)cJSON_GetNumberValue(rsi);
}
/* follow_graph_refresh_seconds */
cJSON *fgr = cJSON_GetObjectItem(root, "follow_graph_refresh_seconds");
if (fgr) cfg->follow_graph_refresh_seconds = (int)cJSON_GetNumberValue(fgr);
/* state */
cJSON *st = cJSON_GetObjectItem(root, "state");
if (st) {
cJSON *bu = cJSON_GetObjectItem(st, "backfilled_until");
if (bu) cfg->state.backfilled_until = (long)cJSON_GetNumberValue(bu);
}
cJSON_Delete(root);
/* Defaults if missing. */
if (cfg->backfill.events_per_tick == 0) cfg->backfill.events_per_tick = 500;
if (cfg->backfill.tick_interval_seconds == 0) cfg->backfill.tick_interval_seconds = 5;
if (cfg->live.resubscribe_interval_seconds == 0) cfg->live.resubscribe_interval_seconds = 300;
if (cfg->follow_graph_refresh_seconds == 0) cfg->follow_graph_refresh_seconds = 600;
/* Validation. */
if (cfg->root_npub_count == 0) {
DEBUG_ERROR("config: at least one root_npub required");
return -1;
}
if (cfg->upstream_count == 0) {
DEBUG_ERROR("config: at least one upstream_relay required");
return -1;
}
if (cfg->local_relay[0] == '\0') {
DEBUG_ERROR("config: local_relay required");
return -1;
}
if (cfg->kind_count == 0) {
DEBUG_ERROR("config: at least one kind required");
return -1;
}
DEBUG_INFO("config loaded: %d root npubs, %d upstream relays, %d kinds",
cfg->root_npub_count, cfg->upstream_count, cfg->kind_count);
return 0;
}
/* Build a fresh cJSON tree from the in-memory config and write it out.
* We re-emit plain JSON (comments are not preserved on rewrite). */
int cr_config_save_state(cr_config_t *cfg) {
cJSON *root = cJSON_CreateObject();
cJSON *npubs = cJSON_CreateArray();
for (int i = 0; i < cfg->root_npub_count; i++)
cJSON_AddItemToArray(npubs, cJSON_CreateString(cfg->root_npubs[i]));
cJSON_AddItemToObject(root, "root_npubs", npubs);
cJSON *upstream = cJSON_CreateArray();
for (int i = 0; i < cfg->upstream_count; i++)
cJSON_AddItemToArray(upstream, cJSON_CreateString(cfg->upstream_relays[i]));
cJSON_AddItemToObject(root, "upstream_relays", upstream);
cJSON_AddItemToObject(root, "local_relay", cJSON_CreateString(cfg->local_relay));
cJSON *kinds = cJSON_CreateArray();
for (int i = 0; i < cfg->kind_count; i++)
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(cfg->kinds[i]));
cJSON_AddItemToObject(root, "kinds", kinds);
/* admin_kinds */
cJSON *akinds = cJSON_CreateArray();
if (cfg->admin_all_kinds) {
cJSON_AddItemToArray(akinds, cJSON_CreateString("*"));
} else {
for (int i = 0; i < cfg->admin_kind_count; i++)
cJSON_AddItemToArray(akinds, cJSON_CreateNumber(cfg->admin_kinds[i]));
}
cJSON_AddItemToObject(root, "admin_kinds", akinds);
cJSON *bf = cJSON_CreateObject();
cJSON_AddItemToObject(bf, "enabled", cJSON_CreateBool(cfg->backfill.enabled));
cJSON_AddItemToObject(bf, "events_per_tick", cJSON_CreateNumber(cfg->backfill.events_per_tick));
cJSON_AddItemToObject(bf, "tick_interval_seconds", cJSON_CreateNumber(cfg->backfill.tick_interval_seconds));
cJSON_AddItemToObject(root, "backfill", bf);
cJSON *lv = cJSON_CreateObject();
cJSON_AddItemToObject(lv, "enabled", cJSON_CreateBool(cfg->live.enabled));
cJSON_AddItemToObject(lv, "resubscribe_interval_seconds", cJSON_CreateNumber(cfg->live.resubscribe_interval_seconds));
cJSON_AddItemToObject(root, "live", lv);
cJSON_AddItemToObject(root, "follow_graph_refresh_seconds",
cJSON_CreateNumber(cfg->follow_graph_refresh_seconds));
cJSON *st = cJSON_CreateObject();
cJSON_AddItemToObject(st, "backfilled_until", cJSON_CreateNumber(cfg->state.backfilled_until));
cJSON_AddItemToObject(root, "state", st);
char *json = cJSON_Print(root);
cJSON_Delete(root);
if (!json) {
DEBUG_ERROR("config save: failed to serialize");
return -1;
}
/* Atomic write: temp file + rename. */
char tmp[1100];
snprintf(tmp, sizeof(tmp), "%s.tmp", cfg->path);
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
DEBUG_ERROR("config save: cannot open tmp '%s': %s", tmp, strerror(errno));
free(json);
return -1;
}
size_t jlen = strlen(json);
ssize_t w = write(fd, json, jlen);
close(fd);
free(json);
if (w < 0 || (size_t)w != jlen) {
DEBUG_ERROR("config save: short write");
return -1;
}
if (rename(tmp, cfg->path) != 0) {
DEBUG_ERROR("config save: rename failed: %s", strerror(errno));
return -1;
}
DEBUG_LOG("config state saved: backfilled_until=%ld",
cfg->state.backfilled_until);
return 0;
}
void cr_config_free(cr_config_t *cfg) {
(void)cfg;
}
+79
View File
@@ -0,0 +1,79 @@
/*
* caching_relay - config parsing + persistent state (in the .jsonc file itself)
*/
#ifndef CACHING_RELAY_CONFIG_H
#define CACHING_RELAY_CONFIG_H
#include <stddef.h>
#include <time.h>
/* Maximums to keep things statically sized and simple. */
#define CR_MAX_ROOT_NPUBS 16
#define CR_MAX_UPSTREAM 32
#define CR_MAX_KINDS 32
#define CR_NPUB_LEN 64 /* npub1... bech32, generous */
#define CR_URL_LEN 256
#define CR_HEX_PUBKEY_LEN 65 /* 64 hex chars + NUL */
typedef struct {
int enabled;
int events_per_tick; /* per-query page size (default 500) */
int tick_interval_seconds;
} cr_backfill_config_t;
typedef struct {
int enabled;
int resubscribe_interval_seconds;
} cr_live_config_t;
/* Persistent state. The per-author until-cursor drain model stores all
* backfill progress in the caching_followed_pubkeys table, so the only
* state kept here is the legacy-mode first-time flag (used to drive relay
* discovery caching behavior). */
typedef struct {
long backfilled_until; /* legacy: 0 = first-time startup */
} cr_state_t;
typedef struct {
char root_npubs[CR_MAX_ROOT_NPUBS][CR_NPUB_LEN];
int root_npub_count;
/* Decoded hex pubkeys for root npubs (filled by follow_graph). */
char root_hex[CR_MAX_ROOT_NPUBS][CR_HEX_PUBKEY_LEN];
int root_hex_ready;
char upstream_relays[CR_MAX_UPSTREAM][CR_URL_LEN];
int upstream_count;
char local_relay[CR_URL_LEN];
int kinds[CR_MAX_KINDS];
int kind_count;
/* Kinds to follow specifically for the root (admin) npubs.
* If admin_all_kinds is 1, grab everything (no kind filter). */
int admin_kinds[CR_MAX_KINDS];
int admin_kind_count;
int admin_all_kinds;
cr_backfill_config_t backfill;
cr_live_config_t live;
int follow_graph_refresh_seconds;
cr_state_t state;
/* Path the config was loaded from (for state write-back). */
char path[1024];
} cr_config_t;
/* Load + validate config from a .jsonc file. Returns 0 on success, -1 on error.
* On success cfg->path is set and cfg->state is populated from the file. */
int cr_config_load(cr_config_t *cfg, const char *path);
/* Persist the state sub-object back into the config file (temp + rename).
* Preserves all other config fields. Returns 0 on success, -1 on error. */
int cr_config_save_state(cr_config_t *cfg);
/* Free any heap resources held by cfg (currently none, but kept for future). */
void cr_config_free(cr_config_t *cfg);
#endif /* CACHING_RELAY_CONFIG_H */

Some files were not shown because too many files have changed in this diff Show More