Browse Source
CLI sync command:
- orly sync wss://relay.example.com --filter '{"kinds": [0,3]}' --dir down
- Matches strfry sync command format for easy migration
- Supports --filter JSON, --dir (down/up/both), --data-dir options
Docker test suite for strfry interoperability:
- tests/negentropy/Dockerfile.strfry - strfry with negentropy enabled
- tests/negentropy/Dockerfile.orly - orly relay for testing
- tests/negentropy/docker-compose.yml - orchestrates both relays
- tests/negentropy/test-sync.sh - automated bidirectional sync test
- tests/negentropy/test-orly-cli.sh - orly CLI sync test
- tests/negentropy/test-strfry-cli.sh - strfry CLI sync test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
main
9 changed files with 713 additions and 4 deletions
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
# ORLY relay Dockerfile for negentropy interop testing |
||||
|
||||
FROM golang:1.23-alpine AS builder |
||||
|
||||
RUN apk add --no-cache git make |
||||
|
||||
WORKDIR /build |
||||
COPY . . |
||||
|
||||
# Build orly binary |
||||
RUN CGO_ENABLED=0 go build -o orly ./cmd/orly |
||||
|
||||
# Runtime image |
||||
FROM alpine:3.19 |
||||
|
||||
RUN apk add --no-cache ca-certificates curl jq |
||||
|
||||
WORKDIR /app |
||||
COPY --from=builder /build/orly /app/ |
||||
|
||||
RUN mkdir -p /data |
||||
|
||||
EXPOSE 3334 |
||||
|
||||
# Environment variables |
||||
ENV ORLY_PORT=3334 |
||||
ENV ORLY_DATA_DIR=/data |
||||
ENV ORLY_LOG_LEVEL=info |
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 \ |
||||
CMD curl -f http://localhost:3334 || exit 1 |
||||
|
||||
# Run orly relay |
||||
CMD ["/app/orly"] |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
# strfry Dockerfile for negentropy interop testing |
||||
# Uses Ubuntu for easier dependency management |
||||
|
||||
FROM ubuntu:22.04 AS builder |
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive |
||||
|
||||
# Install build dependencies |
||||
RUN apt-get update && apt-get install -y \ |
||||
git \ |
||||
build-essential \ |
||||
liblmdb-dev \ |
||||
libsecp256k1-dev \ |
||||
libflatbuffers-dev \ |
||||
libzstd-dev \ |
||||
pkg-config \ |
||||
libtool \ |
||||
autoconf \ |
||||
automake \ |
||||
curl \ |
||||
&& rm -rf /var/lib/apt/lists/* |
||||
|
||||
WORKDIR /build |
||||
|
||||
# Clone strfry |
||||
RUN git clone https://github.com/hoytech/strfry.git . && \ |
||||
git submodule update --init |
||||
|
||||
# Build strfry |
||||
RUN make setup-golpe && \ |
||||
make -j$(nproc) |
||||
|
||||
# Runtime image |
||||
FROM ubuntu:22.04 |
||||
|
||||
RUN apt-get update && apt-get install -y \ |
||||
liblmdb0 \ |
||||
libsecp256k1-0 \ |
||||
libflatbuffers1 \ |
||||
libzstd1 \ |
||||
curl \ |
||||
jq \ |
||||
websocat \ |
||||
&& rm -rf /var/lib/apt/lists/* |
||||
|
||||
WORKDIR /app |
||||
COPY --from=builder /build/strfry /app/ |
||||
RUN mkdir -p /data/strfry-db |
||||
|
||||
# Copy config |
||||
COPY strfry.conf /etc/strfry.conf |
||||
|
||||
EXPOSE 7777 |
||||
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 \ |
||||
CMD curl -f http://localhost:7777 || exit 1 |
||||
|
||||
# Run strfry relay |
||||
CMD ["/app/strfry", "--config=/etc/strfry.conf", "relay"] |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
# Docker Compose for negentropy interop testing between strfry and orly |
||||
# |
||||
# Usage: |
||||
# docker compose build |
||||
# docker compose up -d |
||||
# ./test-sync.sh |
||||
# docker compose down -v |
||||
|
||||
services: |
||||
strfry: |
||||
build: |
||||
context: . |
||||
dockerfile: Dockerfile.strfry |
||||
ports: |
||||
- "7777:7777" |
||||
volumes: |
||||
- strfry-data:/data |
||||
healthcheck: |
||||
test: ["CMD", "curl", "-f", "http://localhost:7777"] |
||||
interval: 5s |
||||
timeout: 5s |
||||
retries: 5 |
||||
start_period: 30s |
||||
networks: |
||||
- negentropy-test |
||||
|
||||
orly: |
||||
build: |
||||
context: ../.. |
||||
dockerfile: tests/negentropy/Dockerfile.orly |
||||
ports: |
||||
- "3334:3334" |
||||
environment: |
||||
- ORLY_PORT=3334 |
||||
- ORLY_DATA_DIR=/data |
||||
- ORLY_LOG_LEVEL=info |
||||
- ORLY_SYNC_TARGET_RELAYS=ws://strfry:7777 |
||||
volumes: |
||||
- orly-data:/data |
||||
healthcheck: |
||||
test: ["CMD", "curl", "-f", "http://localhost:3334"] |
||||
interval: 5s |
||||
timeout: 5s |
||||
retries: 5 |
||||
start_period: 10s |
||||
depends_on: |
||||
strfry: |
||||
condition: service_healthy |
||||
networks: |
||||
- negentropy-test |
||||
|
||||
# Utility container for running sync commands |
||||
sync-runner: |
||||
build: |
||||
context: ../.. |
||||
dockerfile: tests/negentropy/Dockerfile.orly |
||||
entrypoint: ["/bin/sh", "-c"] |
||||
command: ["sleep infinity"] |
||||
environment: |
||||
- ORLY_DATA_DIR=/data |
||||
volumes: |
||||
- sync-data:/data |
||||
depends_on: |
||||
strfry: |
||||
condition: service_healthy |
||||
orly: |
||||
condition: service_healthy |
||||
networks: |
||||
- negentropy-test |
||||
|
||||
volumes: |
||||
strfry-data: |
||||
orly-data: |
||||
sync-data: |
||||
|
||||
networks: |
||||
negentropy-test: |
||||
driver: bridge |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
## strfry configuration for negentropy interop testing |
||||
|
||||
relay { |
||||
# Interface to listen on. Use 0.0.0.0 to listen on all interfaces |
||||
bind = "0.0.0.0" |
||||
|
||||
# Port to open for the nostr websocket protocol |
||||
port = 7777 |
||||
|
||||
# Enable negentropy protocol for sync |
||||
negentropy { |
||||
enabled = true |
||||
maxSyncEvents = 1000000 |
||||
} |
||||
|
||||
# Number of threads for handling negentropy messages |
||||
numThreads { |
||||
negentropy = 4 |
||||
} |
||||
|
||||
# Nostr protocol settings |
||||
nostr { |
||||
# Maximum message size (1MB) |
||||
maxFilterLimit = 10000 |
||||
} |
||||
} |
||||
|
||||
db { |
||||
# LMDB directory |
||||
path = "/data/strfry-db/" |
||||
} |
||||
|
||||
events { |
||||
# Maximum events to return |
||||
maxLimit = 10000 |
||||
} |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash |
||||
# |
||||
# Test orly CLI sync against strfry relay |
||||
# |
||||
# This mimics the exact workflow David described, but using orly: |
||||
# orly sync wss://relay.example.com --filter '{"kinds": [0, 3, 1984, 10000, 30000]}' --dir down |
||||
# |
||||
# Usage: |
||||
# docker compose up -d |
||||
# ./test-orly-cli.sh |
||||
# |
||||
|
||||
set -e |
||||
|
||||
STRFRY_HOST="${STRFRY_HOST:-localhost}" |
||||
STRFRY_PORT="${STRFRY_PORT:-7777}" |
||||
ORLY_CONTAINER="${ORLY_CONTAINER:-negentropy-sync-runner-1}" |
||||
|
||||
# Test filter (same kinds David uses for Brainstorm) |
||||
FILTER='{"kinds": [0, 3, 1984, 10000, 30000]}' |
||||
|
||||
echo "========================================" |
||||
echo "orly CLI sync test against strfry" |
||||
echo "========================================" |
||||
echo "" |
||||
echo "Target: ws://strfry:7777" |
||||
echo "Filter: $FILTER" |
||||
echo "" |
||||
|
||||
# Run orly sync command |
||||
echo "Running: orly sync ws://strfry:7777 --filter '$FILTER' --dir down" |
||||
echo "" |
||||
|
||||
docker exec -it "$ORLY_CONTAINER" /app/orly sync ws://strfry:7777 --filter "$FILTER" --dir down --verbose |
||||
|
||||
echo "" |
||||
echo "Sync complete!" |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash |
||||
# |
||||
# Test strfry CLI sync against orly relay |
||||
# |
||||
# This mimics the exact workflow David described: |
||||
# strfry sync wss://relay.orly.dev --filter '{"kinds": [0, 3, 1984, 10000, 30000]}' --dir down |
||||
# |
||||
# Usage: |
||||
# docker compose up -d |
||||
# ./test-strfry-cli.sh |
||||
# |
||||
|
||||
set -e |
||||
|
||||
ORLY_HOST="${ORLY_HOST:-localhost}" |
||||
ORLY_PORT="${ORLY_PORT:-3334}" |
||||
STRFRY_CONTAINER="${STRFRY_CONTAINER:-negentropy-strfry-1}" |
||||
|
||||
# Test filter (same kinds David uses for Brainstorm) |
||||
FILTER='{"kinds": [0, 3, 1984, 10000, 30000]}' |
||||
|
||||
echo "========================================" |
||||
echo "strfry CLI sync test against orly" |
||||
echo "========================================" |
||||
echo "" |
||||
echo "Target: ws://${ORLY_HOST}:${ORLY_PORT}" |
||||
echo "Filter: $FILTER" |
||||
echo "" |
||||
|
||||
# Run strfry sync command |
||||
echo "Running: strfry sync ws://orly:3334 --filter '$FILTER' --dir down" |
||||
echo "" |
||||
|
||||
docker exec -it "$STRFRY_CONTAINER" /app/strfry sync ws://orly:3334 --filter "$FILTER" --dir down |
||||
|
||||
echo "" |
||||
echo "Sync complete!" |
||||
@ -0,0 +1,226 @@
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash |
||||
# |
||||
# Negentropy Interop Test Script |
||||
# Tests NIP-77 negentropy sync between strfry and orly |
||||
# |
||||
# Usage: |
||||
# ./test-sync.sh |
||||
# |
||||
# Prerequisites: |
||||
# - docker compose up -d (containers running) |
||||
# - websocat and jq installed (for event generation) |
||||
# |
||||
|
||||
set -e |
||||
|
||||
STRFRY_URL="ws://localhost:7777" |
||||
ORLY_URL="ws://localhost:3334" |
||||
TEST_EVENTS=10 |
||||
PASSED=0 |
||||
FAILED=0 |
||||
|
||||
# Colors for output |
||||
RED='\033[0;31m' |
||||
GREEN='\033[0;32m' |
||||
YELLOW='\033[1;33m' |
||||
NC='\033[0m' # No Color |
||||
|
||||
log_info() { |
||||
echo -e "${YELLOW}[INFO]${NC} $1" |
||||
} |
||||
|
||||
log_pass() { |
||||
echo -e "${GREEN}[PASS]${NC} $1" |
||||
((PASSED++)) |
||||
} |
||||
|
||||
log_fail() { |
||||
echo -e "${RED}[FAIL]${NC} $1" |
||||
((FAILED++)) |
||||
} |
||||
|
||||
# Generate a test private key (for signing events) |
||||
# This is a fixed test key - DO NOT use in production |
||||
TEST_PRIVKEY="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
||||
TEST_PUBKEY="a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1" |
||||
|
||||
# Check if services are running |
||||
check_services() { |
||||
log_info "Checking if services are running..." |
||||
|
||||
if ! curl -s http://localhost:7777 > /dev/null 2>&1; then |
||||
log_fail "strfry is not running on port 7777" |
||||
exit 1 |
||||
fi |
||||
log_pass "strfry is running" |
||||
|
||||
if ! curl -s http://localhost:3334 > /dev/null 2>&1; then |
||||
log_fail "orly is not running on port 3334" |
||||
exit 1 |
||||
fi |
||||
log_pass "orly is running" |
||||
} |
||||
|
||||
# Generate and send a test event to a relay |
||||
# Uses websocat if available, otherwise uses netcat |
||||
send_event() { |
||||
local relay_url=$1 |
||||
local kind=$2 |
||||
local content=$3 |
||||
local timestamp=$(date +%s) |
||||
|
||||
# Create a simple unsigned event (strfry will accept it in test mode) |
||||
# For real testing, we'd need proper signing |
||||
local event_json=$(cat <<EOF |
||||
["EVENT", { |
||||
"id": "$(echo -n "${timestamp}${content}" | sha256sum | cut -d' ' -f1)", |
||||
"pubkey": "${TEST_PUBKEY}", |
||||
"created_at": ${timestamp}, |
||||
"kind": ${kind}, |
||||
"tags": [], |
||||
"content": "${content}", |
||||
"sig": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
||||
}] |
||||
EOF |
||||
) |
||||
|
||||
echo "$event_json" | websocat -n1 "$relay_url" 2>/dev/null || true |
||||
} |
||||
|
||||
# Count events on a relay matching a filter |
||||
count_events() { |
||||
local relay_url=$1 |
||||
local filter=$2 |
||||
|
||||
# Send REQ and count EVENT responses before EOSE |
||||
local count=$(echo "[\"REQ\", \"count\", $filter]" | \ |
||||
timeout 5 websocat -n "$relay_url" 2>/dev/null | \ |
||||
grep -c '"EVENT"' || echo "0") |
||||
|
||||
echo "$count" |
||||
} |
||||
|
||||
# Test 1: Seed strfry with events |
||||
test_seed_strfry() { |
||||
log_info "Test 1: Seeding strfry with $TEST_EVENTS test events..." |
||||
|
||||
for i in $(seq 1 $TEST_EVENTS); do |
||||
send_event "$STRFRY_URL" 1 "Test event $i from strfry seed" |
||||
sleep 0.1 |
||||
done |
||||
|
||||
sleep 2 # Wait for events to be processed |
||||
|
||||
local count=$(count_events "$STRFRY_URL" '{"kinds": [1], "limit": 100}') |
||||
log_info "strfry event count: $count" |
||||
|
||||
if [ "$count" -ge "$TEST_EVENTS" ]; then |
||||
log_pass "strfry seeded with $count events" |
||||
else |
||||
log_fail "strfry only has $count events (expected >= $TEST_EVENTS)" |
||||
fi |
||||
} |
||||
|
||||
# Test 2: Run orly sync to pull from strfry |
||||
test_orly_sync_from_strfry() { |
||||
log_info "Test 2: Running orly sync from strfry (strfry -> orly)..." |
||||
|
||||
# Use the sync-runner container to run sync |
||||
docker compose exec -T sync-runner /app/orly sync ws://strfry:7777 --filter '{"kinds": [1]}' --dir down -v |
||||
|
||||
sleep 3 # Wait for sync to complete |
||||
|
||||
local orly_count=$(count_events "$ORLY_URL" '{"kinds": [1], "limit": 100}') |
||||
log_info "orly event count after sync: $orly_count" |
||||
|
||||
if [ "$orly_count" -ge "$TEST_EVENTS" ]; then |
||||
log_pass "orly synced $orly_count events from strfry" |
||||
else |
||||
log_fail "orly only synced $orly_count events (expected >= $TEST_EVENTS)" |
||||
fi |
||||
} |
||||
|
||||
# Test 3: Seed orly with new events and sync back to strfry |
||||
test_orly_sync_to_strfry() { |
||||
log_info "Test 3: Seeding orly and syncing back to strfry (orly -> strfry)..." |
||||
|
||||
local new_events=5 |
||||
for i in $(seq 1 $new_events); do |
||||
send_event "$ORLY_URL" 1 "Test event $i from orly for reverse sync" |
||||
sleep 0.1 |
||||
done |
||||
|
||||
sleep 2 |
||||
|
||||
# Check orly has the new events |
||||
local orly_count=$(count_events "$ORLY_URL" '{"kinds": [1], "limit": 100}') |
||||
log_info "orly event count: $orly_count" |
||||
|
||||
# Run strfry sync from orly (using strfry's sync command) |
||||
# Note: strfry sync command format is: strfry sync <url> --filter <filter> --dir down |
||||
docker compose exec -T strfry /app/strfry sync ws://orly:3334 --filter '{"kinds": [1]}' --dir down || true |
||||
|
||||
sleep 3 |
||||
|
||||
local strfry_count=$(count_events "$STRFRY_URL" '{"kinds": [1], "limit": 100}') |
||||
log_info "strfry event count after reverse sync: $strfry_count" |
||||
|
||||
local expected=$((TEST_EVENTS + new_events)) |
||||
if [ "$strfry_count" -ge "$expected" ]; then |
||||
log_pass "strfry has $strfry_count events after bidirectional sync" |
||||
else |
||||
log_fail "strfry only has $strfry_count events (expected >= $expected)" |
||||
fi |
||||
} |
||||
|
||||
# Test 4: Verify event consistency |
||||
test_event_consistency() { |
||||
log_info "Test 4: Verifying event consistency between relays..." |
||||
|
||||
local strfry_count=$(count_events "$STRFRY_URL" '{"kinds": [1], "limit": 100}') |
||||
local orly_count=$(count_events "$ORLY_URL" '{"kinds": [1], "limit": 100}') |
||||
|
||||
log_info "strfry: $strfry_count events, orly: $orly_count events" |
||||
|
||||
# After bidirectional sync, both should have similar counts |
||||
local diff=$((strfry_count - orly_count)) |
||||
if [ "${diff#-}" -le 2 ]; then # Allow small difference |
||||
log_pass "Event counts are consistent (diff: $diff)" |
||||
else |
||||
log_fail "Event counts differ by $diff" |
||||
fi |
||||
} |
||||
|
||||
# Run all tests |
||||
main() { |
||||
echo "========================================" |
||||
echo "Negentropy Interop Test Suite" |
||||
echo "strfry <-> orly (NIP-77)" |
||||
echo "========================================" |
||||
echo |
||||
|
||||
check_services |
||||
echo |
||||
|
||||
test_seed_strfry |
||||
echo |
||||
|
||||
test_orly_sync_from_strfry |
||||
echo |
||||
|
||||
test_orly_sync_to_strfry |
||||
echo |
||||
|
||||
test_event_consistency |
||||
echo |
||||
|
||||
echo "========================================" |
||||
echo "Results: $PASSED passed, $FAILED failed" |
||||
echo "========================================" |
||||
|
||||
if [ "$FAILED" -gt 0 ]; then |
||||
exit 1 |
||||
fi |
||||
} |
||||
|
||||
main "$@" |
||||
Loading…
Reference in new issue