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 @@ |
|||||||
|
# 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 @@ |
|||||||
|
# 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 @@ |
|||||||
|
# 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 @@ |
|||||||
|
## 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 @@ |
|||||||
|
#!/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 @@ |
|||||||
|
#!/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 @@ |
|||||||
|
#!/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