Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ docs/*-cost-model.md
*.tsbuildinfo
next-env.d.ts
.claude/worktrees/

# compiled harness binaries (go build artifacts)
harnesses/*/script
harnesses/*/monitor
harnesses/*/cmd/script/script
harnesses/*/cmd/monitor/monitor
19 changes: 16 additions & 3 deletions harnesses/aggregator-head-lag/cmd/script/head_lag_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ type HeadLagPool struct {
// Pools to monitor - high activity pools for accurate lag measurement
var headLagPools = []HeadLagPool{
{
// Switched 2026-05-29 to the Raydium SOL/USDC ($8.7M liq, $2.6M
// vol24h vs the previous Orca pool 7qbRF6Y… at $206K / $46K).
// This is the only Solana pool GMGN pushes per-pair events for,
// so all 4 providers measure the same reference pool → true
// apples-to-apples instead of GMGN-on-slot-heartbeat vs others-
// on-per-swap. Bonus: much higher event rate tightens stats.
Name: "SOL/USDC Raydium",
Blockchain: "solana",
NetworkID: 1399811149,
Address: "7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm",
Address: "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2",
ChainName: "solana",
},
{
Expand All @@ -42,10 +48,17 @@ var headLagPools = []HeadLagPool{
ChainName: "base",
},
{
Name: "WBNB/BUSD PancakeSwap",
// Switched 2026-05-29 off the WBNB/BUSD pool ($104K vol24h —
// BUSD was deprecated by Binance in 2023 so swap events on it
// are too sparse for percentile stats: the bench was reporting
// zero BNB samples across all 3 regions). New pool is
// PancakeSwap V3 WBNB/USDT 0.01% with $17M liq + $100M vol24h —
// ~1000x event rate, the canonical BNB pair every aggregator
// indexes.
Name: "WBNB/USDT PancakeSwap V3",
Blockchain: "evm:56",
NetworkID: 56,
Address: "0x58f876857a02d6762e0101bb5c46a8c1ed44dc16",
Address: "0x172fcd41e0913e95784454622d1c3724f546f849",
ChainName: "bnb",
},
}
Expand Down
106 changes: 0 additions & 106 deletions harnesses/aggregator-head-lag/cmd/script/log_buffer.go

This file was deleted.

114 changes: 114 additions & 0 deletions harnesses/aggregator-head-lag/cmd/script/loghub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package main

import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"strconv"
"sync"
"time"
)

// Auto-generated by the loghub inline pattern. Captures stdout/stderr into a
// bounded ring buffer and exposes GET /logs?tail=N protected by X-Logs-Token
// matching the LOGS_TOKEN env var.
//
// Keep in sync across miniapps (was previously the shared/loghub package; we
// inline because Railway's per-harness Docker build context can't reach a
// sibling shared module via go.mod replace).

const logRingMax = 5000

type logRing struct {
mu sync.Mutex
lines []string
max int
}

var globalLogRing = &logRing{max: logRingMax}

func (b *logRing) push(line string) {
entry := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + " " + line
b.mu.Lock()
if len(b.lines) >= b.max {
b.lines = append(b.lines[1:], entry)
} else {
b.lines = append(b.lines, entry)
}
b.mu.Unlock()
}

func (b *logRing) snapshot(tail int) []string {
b.mu.Lock()
defer b.mu.Unlock()
if tail <= 0 || tail >= len(b.lines) {
out := make([]string, len(b.lines))
copy(out, b.lines)
return out
}
start := len(b.lines) - tail
out := make([]string, tail)
copy(out, b.lines[start:])
return out
}

var logSetupOnce sync.Once

// installLogCapture replaces os.Stdout (and os.Stderr) with the write-end of a
// pipe, then spawns a goroutine that fan-outs every line to the original
// stdout AND the in-memory ring buffer. Call exactly once, very early in
// main().
func installLogCapture() { logSetupOnce.Do(doInstallLogCapture) }

func doInstallLogCapture() {
originalStdout := os.Stdout
originalStderr := os.Stderr
r, w, err := os.Pipe()
if err != nil {
fmt.Fprintf(originalStdout, "[loghub] pipe failed: %v (/logs will be empty)\n", err)
return
}
os.Stdout = w
os.Stderr = w

go func() {
scanner := bufio.NewScanner(r)
buf := make([]byte, 0, 1024*1024)
scanner.Buffer(buf, 1024*1024)
for scanner.Scan() {
line := scanner.Text()
fmt.Fprintln(originalStdout, line)
globalLogRing.push(line)
}
_, _ = io.Copy(originalStdout, r)
_ = originalStderr
}()
}

// logsHandler returns an http.Handler for GET /logs?tail=N. Requires header
// X-Logs-Token to match the LOGS_TOKEN env var. Returns 403 if env unset.
func logsHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expected := os.Getenv("LOGS_TOKEN")
if expected == "" {
http.Error(w, "logs disabled: LOGS_TOKEN unset", http.StatusForbidden)
return
}
if r.Header.Get("X-Logs-Token") != expected {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
tail := 500
if t := r.URL.Query().Get("tail"); t != "" {
if n, err := strconv.Atoi(t); err == nil && n > 0 {
tail = n
}
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
for _, l := range globalLogRing.snapshot(tail) {
fmt.Fprintln(w, l)
}
})
}
4 changes: 2 additions & 2 deletions harnesses/aggregator-head-lag/cmd/script/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,11 @@ func StartMetricsServer(addr string) error {
// Prometheus metrics endpoint
mux.Handle("/metrics", promhttp.Handler())

mux.Handle("/logs", logsHandler())
// Admin cleanup endpoint
setupCleanupEndpoint(mux)

// Debug: tail of in-memory log ring
setupLogsEndpoint(mux)
// Debug: tail of in-memory log ring (shared loghub package)

return http.ListenAndServe(addr, mux)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Auto-update all RecordX calls to include region parameter

FILES="head_lag_monitor.go codex_rest_monitor.go mobula_rest_monitor.go quote_api_monitor.go geckoterminal_monitor.go moralis_rest_monitor.go metadata_coverage_monitor.go"

for file in $FILES; do
if [ -f "$file" ]; then
echo "Updating $file..."
# Use perl for multiline regex
perl -i -pe 's/RecordHeadLag\(([^)]+)\)/RecordHeadLag($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordRESTLatency\(([^)]+)\)/RecordRESTLatency($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordRESTError\(([^)]+)\)/RecordRESTError($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordQuoteAPILatency\(([^)]+)\)/RecordQuoteAPILatency($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordQuoteAPIError\(([^)]+)\)/RecordQuoteAPIError($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordHeadLagError\(([^)]+)\)/RecordHeadLagError($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordCodexBlockNumber\(([^)]+)\)/RecordCodexBlockNumber($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordMetadataCoverage\(([^)]+)\)/RecordMetadataCoverage($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordMetadataLatency\(([^)]+)\)/RecordMetadataLatency($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordPoolDiscoveryLatency\(([^)]+)\)/RecordPoolDiscoveryLatency($1, config.MonitorRegion)/g' "$file"
perl -i -pe 's/RecordPoolDiscoveryError\(([^)]+)\)/RecordPoolDiscoveryError($1, config.MonitorRegion)/g' "$file"
fi
done

echo "✓ All files updated"
5 changes: 1 addition & 4 deletions harnesses/bridge-monitor/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ RUN go mod download
# Copy source code
COPY . .

# Build the monitor binary (continuous quote loop + execution scheduler
# that emits all the Prometheus metrics consumed by bridge-quote-latency
# and bridge-fee). The rebalance utility lives at ./cmd/rebalance and can
# be built separately with `go build ./cmd/rebalance`.
# Build the binary
RUN CGO_ENABLED=0 GOOS=linux go build -o /bridge-monitor ./cmd/monitor

# Final stage
Expand Down
Loading
Loading