Files
amethyst/relayBench/build.gradle.kts
Claude 5f3a790d56 feat: add relayBench — head-to-head relay benchmark (geode vs strfry vs any)
New :relayBench module that boots relay implementations as real external
processes under equivalent setups (persistent storage, sig verification on,
no auth) and compares them on the same corpus:

- Ingest: receipt->queryable-by-REQ latency (publish on one connection,
  hammer-poll REQ{ids} on another), OK-ack latency, and pipelined corpus
  replay throughput over N connections.
- Queries: client-realistic filters derived from the corpus (home feed,
  thread, notifications, profiles, hashtag, by-ids, ...), time-to-EOSE
  percentiles plus aggregate events/sec under concurrency; result-set
  counts are cross-checked between relays and mismatches flagged.
- NIP-77 negentropy sync between every relay pair: 80%/80% slices with 60%
  overlap, reconcile timing/rounds/wire-bytes per server side, delta
  transfer, steady-state identical-set reconcile, convergence verified.
- Storage footprint after full ingest.

Corpora (all cached as NDJSON + manifest with a sha256 id fingerprint so
results are comparable across runs and machines):
- synthetic (default): deterministic to the byte — seeded keys, fixed
  timestamps, seed-derived BIP-340 aux nonces — with a realistic social
  shape (zipf authors, threads, reactions, reposts, zap request/receipt
  pairs, hashtags);
- the checked-in real dump (quartz test fixture, ~31k unique 2024 events);
- external dumps (NDJSON or JSON array, gzip sniffed by magic bytes),
  e.g. the 2.1M contact-list archive, with --max-event-bytes/--max-tags
  raising both the corpus filter and the strfry config together;
- live download from public relays.

Every source runs through the same preparation: dedup, drop unsigned/
ephemeral/kind-5, enforce relay ingest caps, parallel Schnorr verify,
chronological sort.

relayBench/run.sh is the one-command entry point: builds geode + harness,
resolves strfry (STRFRY_BIN, PATH, or source build into .cache), runs the
suite and renders an ANSI report with per-metric bars and winners, plus
report.md and results.json under relayBench/results/<timestamp>/.

The harness client disables Nagle (TCP_NODELAY): with the JDK default, a
REQ following the previous round's CLOSE stalls a full delayed-ACK
interval and every latency floors at ~44 ms against both geode and strfry
(verified: ~0.3 ms with it off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeoCvXnTxsKzqurkmjdC46
2026-07-03 18:37:16 +00:00

39 lines
1.1 KiB
Plaintext

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.jetbrainsKotlinJvm)
application
}
application {
mainClass.set("com.vitorpamplona.relaybench.MainKt")
applicationName = "relaybench"
// Percentile latencies get skewed by GC pauses in the *client* — give the
// harness enough heap that it never becomes the bottleneck being measured.
applicationDefaultJvmArgs = listOf("-Xmx2g")
}
kotlin {
jvmToolchain(21)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
dependencies {
// Event model, Schnorr signing, Filter + wire-format JSON. The harness
// reuses quartz so the corpus is byte-identical to what Amethyst emits.
implementation(project(":quartz"))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.jackson.module.kotlin)
implementation(libs.okhttp)
// JNI secp256k1 backend quartz needs at runtime on plain JVM.
runtimeOnly(libs.secp256k1.kmp.jni.jvm)
testImplementation(libs.kotlin.test)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.secp256k1.kmp.jni.jvm)
}