From 4b05d2d9d054c3837b581ea4c1b022f4c24753a5 Mon Sep 17 00:00:00 2001 From: Juan Olveira Date: Wed, 24 Jun 2026 18:07:09 +0000 Subject: [PATCH 1/4] docs: add Edge Connection guide (shred forwarding + market data WebSocket) --- docs/Edge Market Data Connection.md | 465 ++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 466 insertions(+) create mode 100644 docs/Edge Market Data Connection.md diff --git a/docs/Edge Market Data Connection.md b/docs/Edge Market Data Connection.md new file mode 100644 index 0000000..1d40aa7 --- /dev/null +++ b/docs/Edge Market Data Connection.md @@ -0,0 +1,465 @@ +--- +description: Run doublezero-edge-connect to re-forward Solana shreds to a local UDP port and consume normalized Edge market data over a local WebSocket. +--- + +# Edge Connection + +!!! warning "By connecting to DoubleZero I agree to the [DoubleZero Terms of Use](https://doublezero.xyz/terms-protocol). The data is for your internal purposes only and may not be retransmitted (see Section 2(e))." + +`doublezero-edge-connect` is a bridge that joins the **DoubleZero Edge binary multicast** and re-serves it locally as two feeds: + +1. **Solana shred forwarding** — deduplicated (optionally signature-verified) shreds fanned out to one or more local UDP destinations, ready for your validator or RPC. +2. **Normalized market data** — Edge venue feeds decoded, precision-corrected, and re-served as a single JSON WebSocket on `ws://host:8081`. + +Both run from the same container and the same one-line install. Enable whichever feeds your onchain authorization grants. + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## Requirements + +- **Linux/amd64** host with a public IPv4 address authorized onchain for the target environment. +- **Docker** (the one-liner installs it if missing). +- **GRE connectivity** — allow IP protocol 47 at your cloud provider; on AWS disable the ENI source/dest check. +- A **DoubleZero access secret**: a `DZ_`-prefixed base64 token or a path to a keypair file, obtained from the [DoubleZero onboarding](setup.md) process. + +--- + +## Step 1: Install and Run + +One command prepares the host and starts the bridge container. It joins the DoubleZero network and starts every feed your authorization grants — shred forwarding and/or the market-data WebSocket on `:8081`: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (private)" + + ```bash + # Requires a GHCR token with read:packages + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +What the script does: + +1. Checks that the host is Linux/amd64, ensures Docker is present (offers to install it). +2. Prepares the host kernel for the GRE tunnel: loads `tun`/`ip_gre`, raises `net.core.rmem_max`, warns about firewall and cloud-provider rules. +3. Loads your access secret (prompted once if `DZ_SECRET` is not set). +4. Runs the bridge container (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) and executes `doublezero connect multicast`. + +!!! tip "Non-interactive install" + Set `DZ_SECRET=DZ_…` before the pipe to run completely unattended — no prompts at all. + +--- + +## Step 2: Configure + +All configuration is via **environment variables set before the pipe**. There is no config file. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### Installer variables + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DZ_SECRET` | *(prompted)* | `DZ_`-prefixed base64 token **or** path to a keypair file. A token is injected into the container and never written to disk; a file is bind-mounted read-only. | +| `DZ_ENV` | per script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | per script | Override the container image. | +| `DZ_NAME` | `doublezero-edge-connect` | Container name. | +| `DZ_FEEDS` | *(all)* | Comma-separated venues to narrow market-data ingestion (e.g. `VenueA,VenueB`). Does not affect Solana shred forwarding. | +| `DZ_ASSUME_YES` | `0` | Skip confirmation prompts (e.g. the Docker install prompt). | +| `DZ_GHCR_TOKEN` | — | **Devnet only** — a GHCR token with `read:packages` (devnet image is private). | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet only** — GHCR username for the login. | + +### Bridge variables + +The installer forwards **any non-empty** bridge variable straight through to the container. Common ones: + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DZ_IFACE` | `doublezero1` | Network interface to listen on. | +| `DZ_RECV_BUF` | — | UDP receive buffer override (bytes). | +| `METRICS_BIND` | *(empty / off)* | Enable the Prometheus `/metrics` endpoint (e.g. `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | Log level (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Local UDP destination(s) for forwarded shreds — see [Solana Shred Forwarding](#solana-shred-forwarding). | +| `WS_BIND` | `0.0.0.0:8081` | Market-data WebSocket bind address — see [Market Data WebSocket](#market-data-websocket). | +| `WS_MAX_CLIENTS` | `64` | Maximum concurrent WebSocket clients. | +| `WS_INPUT_COINS` | *(empty / off)* | Enable the public WebSocket backstop for listed symbols (e.g. `BTC,ETH`). | + +**Examples:** + +```bash +# Forward shreds to a local validator/RPC: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Non-interactive, testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# Narrow market data to specific venues, verbose logging, non-default WS port: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Enable metrics and a public WS backstop: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + Because the installer only forwards **non-empty** values, you cannot pass an empty override (e.g. `WS_BIND=""` to disable the WebSocket sink) through the one-liner. Use a hand-written `docker run` for that — see [Self-hosting](#advanced-self-hosting). + +--- + +## Solana Shred Forwarding + +The bridge joins the `edge-solana-*` shred multicast groups and fans each datagram to one or more local UDP destinations — feeding your validator or RPC directly off the Edge network. It activates automatically on discovery when those groups are present in your authorization. + +```bash +# Default (dedup-only, forward to local port 20000): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# With signature verification: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destination(s) for forwarded shreds (repeatable). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (one copy per shred), `sigverify` (+ ed25519 verification), `none` (all datagrams). | +| `DZ_SHRED_RPC_URL` | — | Solana RPC endpoint; required by `sigverify` mode. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Size of the dedup window. | + +See [Shred forwarding](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) for the full pipeline and caveats. + +--- + +## Market Data WebSocket + +Open a WebSocket to `ws://:8081` and read JSON frames. You receive all venues you are authorized for. An optional `subscribe` message narrows the stream to specific venues and symbols. + +Any engine that speaks WebSocket + JSON can consume it with a thin (~50–100 line) adapter. The binary multicast, the two-port per-venue split, and the manifest/precision handshake all stay inside the bridge; the only contract a consumer codes against is the WebSocket JSON. + +### Connection lifecycle + +On each new connection the bridge: + +1. **Replays current instrument definitions** — one `instrument` message per known symbol — so the consumer has precision before the first quote. +2. **Replays the latest depth snapshot** per symbol (if the Market-by-Order feed is active). +3. **Streams** `quote` / `trade` / `midpoint` / `depth` messages as they arrive, fanned out to all connected consumers. + +``` +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +``` + +### Message types + +Every message is a JSON object tagged by a `type` field: + +| `type` | Meaning | +|--------|---------| +| `instrument` | Instrument/precision definition. | +| `quote` | Top-of-book update (full state). | +| `trade` | Trade print (last sale). | +| `midpoint` | Derived mid price. | +| `depth` | Full order-book depth snapshot. | +| `status` | Venue-level feed-health transition. | + +Consumers **must ignore unknown `type` values and unknown fields** (forward compatibility). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +Sent on connect and whenever definitions change. `price_exponent` and `qty_exponent` give the venue's tick size and size step as powers of ten. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +Every `quote` is **full state** — a dropped message self-heals on the next quote, no resync needed. The four timestamps decompose end-to-end latency: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off +``` + +`0` is the sentinel for "not available" — treat it as missing, not as 1970. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` is `"buy"`, `"sell"`, or `"unknown"`. Trades are point-in-time events and are not replayed on reconnect. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`bids` are sorted highest price first; `asks` are sorted lowest price first. Each `depth` is a **full snapshot** — replace, do not merge. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +Emitted on the edge when a venue's quote multicast goes silent (`state:"down"`) or recovers (`state:"ok"`). Use it to gray out a venue in your UI. Quote delivery is not gated on status — the feed self-heals on the next quote. + +### Subscriptions + +By default you receive everything. Send a control message to narrow the stream: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Omitting a field matches any value (`{"symbol":"SOL"}` = SOL on every venue). `venue` is matched case-insensitively. + +**Server acknowledgement:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Errors return `{"channel":"error","error":""}`. + +### Heartbeat and liveness + +- The server sends a **WebSocket Ping** every 20 seconds; compliant clients auto-reply Pong. +- Clients silent for 60 seconds are closed and reaped. +- App-level keepalive: `{"method":"ping"}` → `{"channel":"pong"}`. + +### Consumer skeleton + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # unknown types: silently ignore (forward compatibility) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### Input sources and the WebSocket backstop + +The Edge multicast feed is always-on. An optional **public WebSocket backstop** can fill gaps when the Edge feed stalls: + +```bash +# Enable the backstop for BTC and ETH: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +The two sources race per `(venue, symbol, source_ts)` tick inside a shared arbiter. In steady state the Edge source wins (sub-ms vs. tens of ms over the internet); when the Edge gaps, the public copy fills in. The WebSocket output is identical regardless of which source delivered a given update. + +--- + +## Manage the Container + +```bash +# Stream logs +sudo docker logs -f doublezero-edge-connect + +# Check tunnel status +sudo docker exec -it doublezero-edge-connect doublezero status + +# Check device latencies +sudo docker exec -it doublezero-edge-connect doublezero latency + +# Stop and remove +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "No TLS" + The bridge targets a trusted/local network. Terminate TLS at a reverse proxy if you expose the WebSocket endpoint externally. + +--- + +## Monitoring (Prometheus Metrics) + +The metrics endpoint is **off by default**. Enable it with `METRICS_BIND`: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Then scrape: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +Key metrics: + +| Metric | What it tracks | +|--------|---------------| +| `dz_feed_up{venue}` | `1` while that venue's multicast is live, `0` while silent. | +| `dz_datagrams_received_total{venue}` | Ingest volume per venue. | +| `dz_emit_total{venue,kind}` | Messages broadcast after dedup, by type. | +| `dz_quotes_dropped_total{venue}` | Stale/duplicate quotes suppressed. | +| `dz_ws_clients` | Currently connected WebSocket clients. | +| `dz_ws_messages_sent_total{kind}` | Messages forwarded to clients. | +| `dz_ws_client_lagged_total` | Times a slow client was shed to protect the feed. | + +A `GET /healthz` liveness probe is also served on the same bind address. + +--- + +## Advanced: Self-hosting + +The container is available on GHCR: + +| Environment | Image | Tag | +|-------------|-------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (private) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +Run it by hand (required for options the installer can't forward, like `WS_BIND=""`): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**Build from source:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +A larger kernel receive buffer is recommended for bursty feeds: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## Limits and Backpressure + +| Limit | Default | Behavior when exceeded | +|-------|---------|------------------------| +| Concurrent clients (`WS_MAX_CLIENTS`) | 64 | New connection is rejected. | +| Subscriptions per client (`WS_MAX_SUBS`) | 256 | `subscribe` is refused with an error. | +| Inbound control msgs / client / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Client is disconnected. | +| Broadcast buffer (`WS_BROADCAST_CAPACITY`) | 4096 | A slow client **drops the oldest messages** (never stalls the feed). | + +Because every `quote` and `depth` is full state, a consumer that drops messages under backpressure self-heals on the next message — no resync handshake required. + +--- + +## Troubleshooting + +### No shreds arriving at the local port + +- Confirm your access is authorized for the `edge-solana-*` shred groups onchain. +- Verify the tunnel is up: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Check logs for join errors: `sudo docker logs -f doublezero-edge-connect` +- Confirm `DZ_SHRED_FORWARD` points at a reachable local UDP destination. + +### No messages from a venue + +- Verify the tunnel is up: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Check logs for join errors: `sudo docker logs -f doublezero-edge-connect` +- Confirm your access is authorized for that venue onchain. +- Narrow ingestion to that venue with `DZ_FEEDS=` to isolate the issue. + +### WebSocket connects but no quotes arrive + +- The `instrument` messages always arrive first; quotes follow once the reference-data handshake completes. Wait 10–20 seconds after connect before concluding data is missing. +- Check `dz_feed_up{venue}` in metrics — `0` means the multicast is silent on your host. +- Verify firewall rules allow multicast UDP on the `doublezero1` interface. + +### High `dz_ws_client_lagged_total` + +Your consumer is reading slower than the bridge is publishing. Increase the broadcast buffer with `WS_BROADCAST_CAPACITY`, reduce per-message processing time, or add a dedicated reader thread. + +### Container exits immediately + +- The bridge requires `--network host` and the `/dev/net/tun` device; a plain `docker run` without those flags will fail. +- Use the installer one-liner or the exact `docker run` command shown in [Self-hosting](#advanced-self-hosting). + +### GRE tunnel not establishing + +Refer to [Troubleshooting](troubleshooting.md) and ensure IP protocol 47 is permitted at your cloud provider. On AWS, disable the ENI source/dest check for the host. diff --git a/mkdocs.yml b/mkdocs.yml index 5c8c602..606ad9c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,6 +69,7 @@ nav: - Validator Rewards: Validator Rewards.md - Other Multicast Connection: Other Multicast Connection.md - Edge Subscriber Connection: Edge Subscriber Connection.md + - Edge Connection: Edge Market Data Connection.md - Troubleshooting: troubleshooting.md - Shelby: - Shelby Connection: Shelby Permissioned Connection.md From f101bd9b0571fd577c7ea8ac90d71d176420fcd7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:24:17 +0000 Subject: [PATCH 2/4] chore: auto-translate docs --- docs/Edge Market Data Connection.es.md | 465 +++++++++++++++++++++++++ docs/Edge Market Data Connection.fr.md | 465 +++++++++++++++++++++++++ docs/Edge Market Data Connection.it.md | 465 +++++++++++++++++++++++++ docs/Edge Market Data Connection.ja.md | 463 ++++++++++++++++++++++++ docs/Edge Market Data Connection.ko.md | 436 +++++++++++++++++++++++ docs/Edge Market Data Connection.pt.md | 465 +++++++++++++++++++++++++ docs/Edge Market Data Connection.zh.md | 465 +++++++++++++++++++++++++ 7 files changed, 3224 insertions(+) create mode 100644 docs/Edge Market Data Connection.es.md create mode 100644 docs/Edge Market Data Connection.fr.md create mode 100644 docs/Edge Market Data Connection.it.md create mode 100644 docs/Edge Market Data Connection.ja.md create mode 100644 docs/Edge Market Data Connection.ko.md create mode 100644 docs/Edge Market Data Connection.pt.md create mode 100644 docs/Edge Market Data Connection.zh.md diff --git a/docs/Edge Market Data Connection.es.md b/docs/Edge Market Data Connection.es.md new file mode 100644 index 0000000..dbde7df --- /dev/null +++ b/docs/Edge Market Data Connection.es.md @@ -0,0 +1,465 @@ +--- +description: Ejecute doublezero-edge-connect para reenviar shreds de Solana a un puerto UDP local y consumir datos de mercado normalizados de Edge a través de un WebSocket local. +--- + +# Conexión Edge + +!!! warning "Al conectarme a DoubleZero acepto los [Términos de Uso de DoubleZero](https://doublezero.xyz/terms-protocol). Los datos son únicamente para sus propósitos internos y no pueden ser retransmitidos (ver Sección 2(e))." + +`doublezero-edge-connect` es un puente que se une al **multicast binario de DoubleZero Edge** y lo re-sirve localmente como dos feeds: + +1. **Reenvío de shreds de Solana** — shreds deduplicados (opcionalmente con verificación de firma) distribuidos a uno o más destinos UDP locales, listos para su validador o RPC. +2. **Datos de mercado normalizados** — feeds de venues de Edge decodificados, con precisión corregida, y re-servidos como un único WebSocket JSON en `ws://host:8081`. + +Ambos se ejecutan desde el mismo contenedor y la misma instalación de una sola línea. Habilite los feeds que su autorización onchain le otorgue. + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## Requisitos + +- Host **Linux/amd64** con una dirección IPv4 pública autorizada onchain para el entorno objetivo. +- **Docker** (el instalador de una línea lo instala si no está presente). +- **Conectividad GRE** — permita el protocolo IP 47 en su proveedor de nube; en AWS deshabilite la verificación de origen/destino del ENI. +- Un **secreto de acceso DoubleZero**: un token base64 con prefijo `DZ_` o una ruta a un archivo de keypair, obtenido del proceso de [incorporación a DoubleZero](setup.md). + +--- + +## Paso 1: Instalar y Ejecutar + +Un solo comando prepara el host e inicia el contenedor puente. Se une a la red DoubleZero e inicia cada feed que su autorización otorgue — reenvío de shreds y/o el WebSocket de datos de mercado en `:8081`: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (privada)" + + ```bash + # Requiere un token GHCR con read:packages + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +Lo que hace el script: + +1. Verifica que el host sea Linux/amd64, se asegura de que Docker esté presente (ofrece instalarlo). +2. Prepara el kernel del host para el túnel GRE: carga `tun`/`ip_gre`, aumenta `net.core.rmem_max`, advierte sobre reglas de firewall y del proveedor de nube. +3. Carga su secreto de acceso (se solicita una vez si `DZ_SECRET` no está configurado). +4. Ejecuta el contenedor puente (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) y ejecuta `doublezero connect multicast`. + +!!! tip "Instalación no interactiva" + Configure `DZ_SECRET=DZ_…` antes del pipe para ejecutar completamente desatendido — sin ningún prompt. + +--- + +## Paso 2: Configurar + +Toda la configuración se realiza mediante **variables de entorno establecidas antes del pipe**. No hay archivo de configuración. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### Variables del instalador + +| Variable | Valor predeterminado | Propósito | +|----------|---------|---------| +| `DZ_SECRET` | *(solicitado)* | Token base64 con prefijo `DZ_` **o** ruta a un archivo de keypair. Un token se inyecta en el contenedor y nunca se escribe en disco; un archivo se monta en modo solo lectura. | +| `DZ_ENV` | por script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | por script | Sobrescribir la imagen del contenedor. | +| `DZ_NAME` | `doublezero-edge-connect` | Nombre del contenedor. | +| `DZ_FEEDS` | *(todos)* | Venues separados por comas para acotar la ingesta de datos de mercado (ej. `VenueA,VenueB`). No afecta el reenvío de shreds de Solana. | +| `DZ_ASSUME_YES` | `0` | Omitir prompts de confirmación (ej. el prompt de instalación de Docker). | +| `DZ_GHCR_TOKEN` | — | **Solo devnet** — un token GHCR con `read:packages` (la imagen de devnet es privada). | +| `DZ_GHCR_USER` | `malbeclabs` | **Solo devnet** — nombre de usuario GHCR para el login. | + +### Variables del puente + +El instalador reenvía **cualquier variable no vacía** del puente directamente al contenedor. Las más comunes: + +| Variable | Valor predeterminado | Propósito | +|----------|---------|---------| +| `DZ_IFACE` | `doublezero1` | Interfaz de red en la que escuchar. | +| `DZ_RECV_BUF` | — | Sobrescritura del buffer de recepción UDP (bytes). | +| `METRICS_BIND` | *(vacío / desactivado)* | Habilitar el endpoint Prometheus `/metrics` (ej. `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | Nivel de log (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destino(s) UDP locales para shreds reenviados — ver [Reenvío de Shreds de Solana](#reenvio-de-shreds-de-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Dirección de enlace del WebSocket de datos de mercado — ver [WebSocket de Datos de Mercado](#websocket-de-datos-de-mercado). | +| `WS_MAX_CLIENTS` | `64` | Máximo de clientes WebSocket concurrentes. | +| `WS_INPUT_COINS` | *(vacío / desactivado)* | Habilitar el respaldo público de WebSocket para los símbolos listados (ej. `BTC,ETH`). | + +**Ejemplos:** + +```bash +# Reenviar shreds a un validador/RPC local: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# No interactivo, testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# Acotar datos de mercado a venues específicos, logging verbose, puerto WS no predeterminado: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Habilitar métricas y un respaldo público de WS: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + Dado que el instalador solo reenvía valores **no vacíos**, no puede pasar una sobrescritura vacía (ej. `WS_BIND=""` para deshabilitar el sink WebSocket) a través del one-liner. Use un `docker run` escrito manualmente para eso — ver [Autoalojamiento](#avanzado-autoalojamiento). + +--- + +## Reenvío de Shreds de Solana + +El puente se une a los grupos multicast `edge-solana-*` de shreds y distribuye cada datagrama a uno o más destinos UDP locales — alimentando su validador o RPC directamente desde la red Edge. Se activa automáticamente al descubrir cuando esos grupos están presentes en su autorización. + +```bash +# Predeterminado (solo dedup, reenviar al puerto local 20000): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Con verificación de firma: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| Variable | Valor predeterminado | Propósito | +|----------|---------|---------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destino(s) para shreds reenviados (repetible). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (una copia por shred), `sigverify` (+ verificación ed25519), `none` (todos los datagramas). | +| `DZ_SHRED_RPC_URL` | — | Endpoint RPC de Solana; requerido por el modo `sigverify`. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Tamaño de la ventana de deduplicación. | + +Ver [Reenvío de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para el pipeline completo y advertencias. + +--- + +## WebSocket de Datos de Mercado + +Abra un WebSocket a `ws://:8081` y lea tramas JSON. Recibirá todos los venues para los que esté autorizado. Un mensaje opcional `subscribe` acota el flujo a venues y símbolos específicos. + +Cualquier motor que hable WebSocket + JSON puede consumirlo con un adaptador ligero (~50–100 líneas). El multicast binario, la división de dos puertos por venue y el handshake de manifiesto/precisión permanecen dentro del puente; el único contrato contra el que un consumidor programa es el JSON del WebSocket. + +### Ciclo de vida de la conexión + +En cada nueva conexión el puente: + +1. **Reproduce las definiciones de instrumentos actuales** — un mensaje `instrument` por cada símbolo conocido — para que el consumidor tenga la precisión antes de la primera cotización. +2. **Reproduce la última instantánea de profundidad** por símbolo (si el feed Market-by-Order está activo). +3. **Transmite** mensajes `quote` / `trade` / `midpoint` / `depth` a medida que llegan, distribuidos a todos los consumidores conectados. + +``` +connect → instrument (×N) → depth (×M, últimos libros) → quote → trade → depth → … +``` + +### Tipos de mensaje + +Cada mensaje es un objeto JSON etiquetado con un campo `type`: + +| `type` | Significado | +|--------|---------| +| `instrument` | Definición de instrumento/precisión. | +| `quote` | Actualización del tope del libro (estado completo). | +| `trade` | Impresión de operación (última venta). | +| `midpoint` | Precio medio derivado. | +| `depth` | Instantánea completa de profundidad del libro de órdenes. | +| `status` | Transición de salud del feed a nivel de venue. | + +Los consumidores **deben ignorar valores `type` desconocidos y campos desconocidos** (compatibilidad hacia adelante). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +Se envía al conectar y cada vez que las definiciones cambian. `price_exponent` y `qty_exponent` indican el tick size y el paso de tamaño del venue como potencias de diez. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +Cada `quote` es **estado completo** — un mensaje perdido se auto-recupera con la siguiente cotización, sin necesidad de resincronización. Las cuatro marcas de tiempo descomponen la latencia de extremo a extremo: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (recepción del consumidor) + libro del venue llegada por cable post-decodificación entrega al WS +``` + +`0` es el valor centinela para "no disponible" — trátelo como ausente, no como 1970. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` es `"buy"`, `"sell"` o `"unknown"`. Las operaciones son eventos puntuales y no se reproducen al reconectar. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +Los `bids` están ordenados del precio más alto primero; los `asks` están ordenados del precio más bajo primero. Cada `depth` es una **instantánea completa** — reemplace, no combine. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +Se emite en el edge cuando el multicast de cotizaciones de un venue queda en silencio (`state:"down"`) o se recupera (`state:"ok"`). Úselo para atenuar un venue en su interfaz. La entrega de cotizaciones no depende del estado — el feed se auto-recupera con la siguiente cotización. + +### Suscripciones + +Por defecto recibe todo. Envíe un mensaje de control para acotar el flujo: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Omitir un campo coincide con cualquier valor (`{"symbol":"SOL"}` = SOL en todos los venues). `venue` se compara sin distinción de mayúsculas/minúsculas. + +**Confirmación del servidor:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Los errores devuelven `{"channel":"error","error":""}`. + +### Heartbeat y comprobación de vida + +- El servidor envía un **Ping de WebSocket** cada 20 segundos; los clientes compatibles responden automáticamente con Pong. +- Los clientes silenciosos durante 60 segundos son cerrados y eliminados. +- Keepalive a nivel de aplicación: `{"method":"ping"}` → `{"channel":"pong"}`. + +### Esqueleto de consumidor + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # tipos desconocidos: ignorar silenciosamente (compatibilidad hacia adelante) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### Fuentes de entrada y el respaldo WebSocket + +El feed multicast de Edge está siempre activo. Un **respaldo público por WebSocket** opcional puede llenar vacíos cuando el feed Edge se detiene: + +```bash +# Habilitar el respaldo para BTC y ETH: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Las dos fuentes compiten por cada tick `(venue, symbol, source_ts)` dentro de un árbitro compartido. En estado estable la fuente Edge gana (sub-ms vs. decenas de ms por internet); cuando Edge tiene vacíos, la copia pública los completa. La salida WebSocket es idéntica independientemente de qué fuente entregó una actualización determinada. + +--- + +## Gestionar el Contenedor + +```bash +# Transmitir logs +sudo docker logs -f doublezero-edge-connect + +# Verificar estado del túnel +sudo docker exec -it doublezero-edge-connect doublezero status + +# Verificar latencias del dispositivo +sudo docker exec -it doublezero-edge-connect doublezero latency + +# Detener y eliminar +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "Sin TLS" + El puente está diseñado para una red confiable/local. Termine TLS en un proxy inverso si expone el endpoint WebSocket externamente. + +--- + +## Monitoreo (Métricas de Prometheus) + +El endpoint de métricas está **desactivado por defecto**. Habilítelo con `METRICS_BIND`: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Luego haga scraping: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +Métricas clave: + +| Métrica | Qué rastrea | +|--------|---------------| +| `dz_feed_up{venue}` | `1` mientras el multicast de ese venue está activo, `0` mientras está en silencio. | +| `dz_datagrams_received_total{venue}` | Volumen de ingesta por venue. | +| `dz_emit_total{venue,kind}` | Mensajes difundidos después de la deduplicación, por tipo. | +| `dz_quotes_dropped_total{venue}` | Cotizaciones obsoletas/duplicadas suprimidas. | +| `dz_ws_clients` | Clientes WebSocket conectados actualmente. | +| `dz_ws_messages_sent_total{kind}` | Mensajes reenviados a los clientes. | +| `dz_ws_client_lagged_total` | Veces que un cliente lento fue descartado para proteger el feed. | + +Una sonda de vida `GET /healthz` también se sirve en la misma dirección de enlace. + +--- + +## Avanzado: Autoalojamiento + +El contenedor está disponible en GHCR: + +| Entorno | Imagen | Etiqueta | +|-------------|-------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (privada) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +Ejecútelo manualmente (necesario para opciones que el instalador no puede reenviar, como `WS_BIND=""`): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**Compilar desde el código fuente:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +Se recomienda un buffer de recepción del kernel más grande para feeds con ráfagas: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## Límites y Contrapresión + +| Límite | Valor predeterminado | Comportamiento cuando se excede | +|-------|---------|------------------------| +| Clientes concurrentes (`WS_MAX_CLIENTS`) | 64 | La nueva conexión es rechazada. | +| Suscripciones por cliente (`WS_MAX_SUBS`) | 256 | El `subscribe` es rechazado con un error. | +| Mensajes de control entrantes / cliente / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | El cliente es desconectado. | +| Buffer de difusión (`WS_BROADCAST_CAPACITY`) | 4096 | Un cliente lento **descarta los mensajes más antiguos** (nunca detiene el feed). | + +Dado que cada `quote` y `depth` es estado completo, un consumidor que descarta mensajes bajo contrapresión se auto-recupera con el siguiente mensaje — no se requiere handshake de resincronización. + +--- + +## Solución de Problemas + +### No llegan shreds al puerto local + +- Confirme que su acceso está autorizado para los grupos de shreds `edge-solana-*` onchain. +- Verifique que el túnel esté activo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Revise los logs en busca de errores de unión: `sudo docker logs -f doublezero-edge-connect` +- Confirme que `DZ_SHRED_FORWARD` apunta a un destino UDP local alcanzable. + +### No hay mensajes de un venue + +- Verifique que el túnel esté activo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Revise los logs en busca de errores de unión: `sudo docker logs -f doublezero-edge-connect` +- Confirme que su acceso está autorizado para ese venue onchain. +- Acote la ingesta a ese venue con `DZ_FEEDS=` para aislar el problema. + +### El WebSocket conecta pero no llegan cotizaciones + +- Los mensajes `instrument` siempre llegan primero; las cotizaciones siguen una vez que se completa el handshake de datos de referencia. Espere 10–20 segundos después de conectar antes de concluir que faltan datos. +- Verifique `dz_feed_up{venue}` en las métricas — `0` significa que el multicast está en silencio en su host. +- Verifique que las reglas del firewall permitan UDP multicast en la interfaz `doublezero1`. + +### Alto `dz_ws_client_lagged_total` + +Su consumidor está leyendo más lento de lo que el puente está publicando. Aumente el buffer de difusión con `WS_BROADCAST_CAPACITY`, reduzca el tiempo de procesamiento por mensaje, o agregue un hilo de lectura dedicado. + +### El contenedor sale inmediatamente + +- El puente requiere `--network host` y el dispositivo `/dev/net/tun`; un `docker run` sin esos flags fallará. +- Use el one-liner del instalador o el comando `docker run` exacto mostrado en [Autoalojamiento](#avanzado-autoalojamiento). + +### El túnel GRE no se establece + +Consulte [Solución de problemas](troubleshooting.md) y asegúrese de que el protocolo IP 47 esté permitido en su proveedor de nube. En AWS, deshabilite la verificación de origen/destino del ENI para el host. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.fr.md b/docs/Edge Market Data Connection.fr.md new file mode 100644 index 0000000..ea530d4 --- /dev/null +++ b/docs/Edge Market Data Connection.fr.md @@ -0,0 +1,465 @@ +--- +description: Exécutez doublezero-edge-connect pour retransmettre les shreds Solana vers un port UDP local et consommer les données de marché normalisées Edge via un WebSocket local. +--- + +# Connexion Edge + +!!! warning "En me connectant à DoubleZero, j'accepte les [Conditions d'utilisation de DoubleZero](https://doublezero.xyz/terms-protocol). Les données sont destinées à votre usage interne uniquement et ne peuvent pas être retransmises (voir Section 2(e))." + +`doublezero-edge-connect` est un pont qui rejoint le **multicast binaire DoubleZero Edge** et le redistribue localement sous forme de deux flux : + +1. **Retransmission de shreds Solana** — shreds dédupliqués (avec vérification optionnelle de signature) distribués vers une ou plusieurs destinations UDP locales, prêts pour votre validateur ou RPC. +2. **Données de marché normalisées** — flux des venues Edge décodés, avec correction de précision, et redistribués sous forme d'un WebSocket JSON unique sur `ws://host:8081`. + +Les deux fonctionnent depuis le même conteneur et la même installation en une seule ligne. Activez les flux que votre autorisation onchain vous accorde. + +``` + ┌─ UDP datagrams ──▶ validateur / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binaire) (dedup · décodage · normalisation) └─ WebSocket (JSON) ──▶ moteur de trading + ws://host:8081 +``` + +--- + +## Prérequis + +- Hôte **Linux/amd64** avec une adresse IPv4 publique autorisée onchain pour l'environnement cible. +- **Docker** (l'installation en une ligne l'installe s'il est absent). +- **Connectivité GRE** — autorisez le protocole IP 47 chez votre fournisseur cloud ; sur AWS, désactivez la vérification source/dest de l'ENI. +- Un **secret d'accès DoubleZero** : un jeton base64 préfixé `DZ_` ou un chemin vers un fichier keypair, obtenu lors du processus d'[onboarding DoubleZero](setup.md). + +--- + +## Étape 1 : Installation et exécution + +Une seule commande prépare l'hôte et démarre le conteneur pont. Il rejoint le réseau DoubleZero et démarre chaque flux que votre autorisation accorde — retransmission de shreds et/ou WebSocket de données de marché sur `:8081` : + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (privé)" + + ```bash + # Nécessite un jeton GHCR avec read:packages + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +Ce que fait le script : + +1. Vérifie que l'hôte est Linux/amd64, s'assure que Docker est présent (propose de l'installer). +2. Prépare le noyau de l'hôte pour le tunnel GRE : charge `tun`/`ip_gre`, augmente `net.core.rmem_max`, avertit concernant les règles de pare-feu et du fournisseur cloud. +3. Charge votre secret d'accès (demandé une fois si `DZ_SECRET` n'est pas défini). +4. Lance le conteneur pont (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) et exécute `doublezero connect multicast`. + +!!! tip "Installation non interactive" + Définissez `DZ_SECRET=DZ_…` avant le pipe pour une exécution entièrement automatique — aucune invite. + +--- + +## Étape 2 : Configuration + +Toute la configuration se fait via des **variables d'environnement définies avant le pipe**. Il n'y a pas de fichier de configuration. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### Variables de l'installateur + +| Variable | Défaut | Objectif | +|----------|--------|----------| +| `DZ_SECRET` | *(demandé)* | Jeton base64 préfixé `DZ_` **ou** chemin vers un fichier keypair. Un jeton est injecté dans le conteneur et n'est jamais écrit sur le disque ; un fichier est monté en bind en lecture seule. | +| `DZ_ENV` | selon le script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | selon le script | Remplacer l'image du conteneur. | +| `DZ_NAME` | `doublezero-edge-connect` | Nom du conteneur. | +| `DZ_FEEDS` | *(tous)* | Venues séparées par des virgules pour restreindre l'ingestion de données de marché (ex. `VenueA,VenueB`). N'affecte pas la retransmission de shreds Solana. | +| `DZ_ASSUME_YES` | `0` | Ignorer les invites de confirmation (ex. l'invite d'installation de Docker). | +| `DZ_GHCR_TOKEN` | — | **Devnet uniquement** — un jeton GHCR avec `read:packages` (l'image devnet est privée). | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet uniquement** — nom d'utilisateur GHCR pour la connexion. | + +### Variables du pont + +L'installateur transmet directement **toute variable de pont non vide** au conteneur. Les plus courantes : + +| Variable | Défaut | Objectif | +|----------|--------|----------| +| `DZ_IFACE` | `doublezero1` | Interface réseau d'écoute. | +| `DZ_RECV_BUF` | — | Remplacement du tampon de réception UDP (octets). | +| `METRICS_BIND` | *(vide / désactivé)* | Activer le endpoint Prometheus `/metrics` (ex. `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | Niveau de log (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destination(s) UDP locale(s) pour les shreds retransmis — voir [Retransmission de shreds Solana](#retransmission-de-shreds-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Adresse de liaison du WebSocket de données de marché — voir [WebSocket de données de marché](#websocket-de-donnees-de-marche). | +| `WS_MAX_CLIENTS` | `64` | Nombre maximum de clients WebSocket simultanés. | +| `WS_INPUT_COINS` | *(vide / désactivé)* | Activer le WebSocket public de secours pour les symboles listés (ex. `BTC,ETH`). | + +**Exemples :** + +```bash +# Retransmettre les shreds vers un validateur/RPC local : +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Non interactif, testnet : +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# Restreindre les données de marché à des venues spécifiques, logs verbeux, port WS non standard : +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Activer les métriques et un WS public de secours : +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + Parce que l'installateur ne transmet que les valeurs **non vides**, vous ne pouvez pas passer un remplacement vide (ex. `WS_BIND=""` pour désactiver le sink WebSocket) via la commande en une ligne. Utilisez un `docker run` écrit manuellement pour cela — voir [Auto-hébergement](#avance-auto-hebergement). + +--- + +## Retransmission de shreds Solana + +Le pont rejoint les groupes multicast de shreds `edge-solana-*` et distribue chaque datagramme vers une ou plusieurs destinations UDP locales — alimentant votre validateur ou RPC directement depuis le réseau Edge. Il s'active automatiquement à la découverte lorsque ces groupes sont présents dans votre autorisation. + +```bash +# Par défaut (dédup uniquement, retransmission vers le port local 20000) : +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Avec vérification de signature : +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| Variable | Défaut | Objectif | +|----------|--------|----------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destination(s) pour les shreds retransmis (répétable). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (une copie par shred), `sigverify` (+ vérification ed25519), `none` (tous les datagrammes). | +| `DZ_SHRED_RPC_URL` | — | Endpoint RPC Solana ; requis par le mode `sigverify`. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Taille de la fenêtre de déduplication. | + +Voir [Retransmission de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) pour le pipeline complet et les mises en garde. + +--- + +## WebSocket de données de marché + +Ouvrez un WebSocket vers `ws://:8081` et lisez les trames JSON. Vous recevez toutes les venues pour lesquelles vous êtes autorisé. Un message `subscribe` optionnel permet de restreindre le flux à des venues et symboles spécifiques. + +Tout moteur compatible WebSocket + JSON peut le consommer avec un adaptateur léger (~50–100 lignes). Le multicast binaire, la séparation deux-ports par venue, et le handshake manifeste/précision restent tous à l'intérieur du pont ; le seul contrat contre lequel un consommateur doit coder est le WebSocket JSON. + +### Cycle de vie de la connexion + +À chaque nouvelle connexion, le pont : + +1. **Rejoue les définitions d'instruments actuelles** — un message `instrument` par symbole connu — afin que le consommateur dispose de la précision avant la première cotation. +2. **Rejoue le dernier snapshot de profondeur** par symbole (si le flux Market-by-Order est actif). +3. **Diffuse** les messages `quote` / `trade` / `midpoint` / `depth` au fur et à mesure de leur arrivée, distribués à tous les consommateurs connectés. + +``` +connexion → instrument (×N) → depth (×M, derniers carnets) → quote → trade → depth → … +``` + +### Types de messages + +Chaque message est un objet JSON identifié par un champ `type` : + +| `type` | Signification | +|--------|---------------| +| `instrument` | Définition d'instrument/précision. | +| `quote` | Mise à jour du meilleur achat/vente (état complet). | +| `trade` | Impression de transaction (dernière vente). | +| `midpoint` | Prix milieu dérivé. | +| `depth` | Snapshot complet de la profondeur du carnet d'ordres. | +| `status` | Transition de santé du flux au niveau de la venue. | + +Les consommateurs **doivent ignorer les valeurs `type` inconnues et les champs inconnus** (compatibilité ascendante). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +Envoyé à la connexion et à chaque modification des définitions. `price_exponent` et `qty_exponent` donnent le pas de cotation et le pas de taille de la venue sous forme de puissances de dix. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +Chaque `quote` est un **état complet** — un message perdu se corrige automatiquement à la prochaine cotation, aucune resynchronisation nécessaire. Les quatre horodatages décomposent la latence de bout en bout : + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (réception consommateur) + carnet venue arrivée fil post-décodage transfert WS +``` + +`0` est la valeur sentinelle pour « non disponible » — traitez-la comme manquante, pas comme 1970. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` est `"buy"`, `"sell"`, ou `"unknown"`. Les transactions sont des événements ponctuels et ne sont pas rejouées à la reconnexion. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +Les `bids` sont triés du prix le plus élevé au plus bas ; les `asks` sont triés du prix le plus bas au plus élevé. Chaque `depth` est un **snapshot complet** — remplacez, ne fusionnez pas. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +Émis en périphérie lorsque le multicast de cotations d'une venue devient silencieux (`state:"down"`) ou récupère (`state:"ok"`). Utilisez-le pour griser une venue dans votre interface. La livraison des cotations n'est pas conditionnée par le statut — le flux se corrige automatiquement à la prochaine cotation. + +### Abonnements + +Par défaut, vous recevez tout. Envoyez un message de contrôle pour restreindre le flux : + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Omettre un champ correspond à toute valeur (`{"symbol":"SOL"}` = SOL sur chaque venue). `venue` est comparé sans tenir compte de la casse. + +**Accusé de réception du serveur :** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Les erreurs retournent `{"channel":"error","error":""}`. + +### Heartbeat et vivacité + +- Le serveur envoie un **Ping WebSocket** toutes les 20 secondes ; les clients conformes répondent automatiquement par un Pong. +- Les clients silencieux pendant 60 secondes sont fermés et supprimés. +- Keepalive au niveau applicatif : `{"method":"ping"}` → `{"channel":"pong"}`. + +### Squelette de consommateur + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # types inconnus : ignorer silencieusement (compatibilité ascendante) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### Sources d'entrée et WebSocket de secours + +Le flux multicast Edge est toujours actif. Un **WebSocket public de secours** optionnel peut combler les lacunes lorsque le flux Edge cale : + +```bash +# Activer le secours pour BTC et ETH : +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Les deux sources sont en concurrence par tick `(venue, symbol, source_ts)` au sein d'un arbitre partagé. En régime permanent, la source Edge gagne (sub-ms contre des dizaines de ms via internet) ; lorsque l'Edge a des lacunes, la copie publique prend le relais. La sortie WebSocket est identique quel que soit la source ayant livré une mise à jour donnée. + +--- + +## Gestion du conteneur + +```bash +# Diffuser les logs +sudo docker logs -f doublezero-edge-connect + +# Vérifier le statut du tunnel +sudo docker exec -it doublezero-edge-connect doublezero status + +# Vérifier les latences des appareils +sudo docker exec -it doublezero-edge-connect doublezero latency + +# Arrêter et supprimer +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "Pas de TLS" + Le pont cible un réseau de confiance/local. Terminez le TLS au niveau d'un reverse proxy si vous exposez le endpoint WebSocket à l'extérieur. + +--- + +## Surveillance (Métriques Prometheus) + +Le endpoint de métriques est **désactivé par défaut**. Activez-le avec `METRICS_BIND` : + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Puis collectez : + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +Métriques clés : + +| Métrique | Ce qu'elle mesure | +|----------|-------------------| +| `dz_feed_up{venue}` | `1` tant que le multicast de cette venue est actif, `0` lorsqu'il est silencieux. | +| `dz_datagrams_received_total{venue}` | Volume d'ingestion par venue. | +| `dz_emit_total{venue,kind}` | Messages diffusés après déduplication, par type. | +| `dz_quotes_dropped_total{venue}` | Cotations obsolètes/dupliquées supprimées. | +| `dz_ws_clients` | Clients WebSocket actuellement connectés. | +| `dz_ws_messages_sent_total{kind}` | Messages transférés aux clients. | +| `dz_ws_client_lagged_total` | Nombre de fois qu'un client lent a été éjecté pour protéger le flux. | + +Une sonde de vivacité `GET /healthz` est également servie sur la même adresse de liaison. + +--- + +## Avancé : Auto-hébergement + +Le conteneur est disponible sur GHCR : + +| Environnement | Image | Tag | +|---------------|-------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (privé) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +Lancez-le manuellement (nécessaire pour les options que l'installateur ne peut pas transmettre, comme `WS_BIND=""`) : + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**Compilation depuis les sources :** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +Un tampon de réception noyau plus grand est recommandé pour les flux en rafales : + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## Limites et contre-pression + +| Limite | Défaut | Comportement en cas de dépassement | +|--------|--------|------------------------------------| +| Clients simultanés (`WS_MAX_CLIENTS`) | 64 | La nouvelle connexion est refusée. | +| Abonnements par client (`WS_MAX_SUBS`) | 256 | Le `subscribe` est refusé avec une erreur. | +| Messages de contrôle entrants / client / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Le client est déconnecté. | +| Tampon de diffusion (`WS_BROADCAST_CAPACITY`) | 4096 | Un client lent **perd les messages les plus anciens** (ne bloque jamais le flux). | + +Parce que chaque `quote` et `depth` est un état complet, un consommateur qui perd des messages sous contre-pression se corrige automatiquement au prochain message — aucun handshake de resynchronisation requis. + +--- + +## Dépannage + +### Aucun shred n'arrive au port local + +- Confirmez que votre accès est autorisé pour les groupes de shreds `edge-solana-*` onchain. +- Vérifiez que le tunnel est actif : `sudo docker exec -it doublezero-edge-connect doublezero status` +- Vérifiez les erreurs de jointure dans les logs : `sudo docker logs -f doublezero-edge-connect` +- Confirmez que `DZ_SHRED_FORWARD` pointe vers une destination UDP locale accessible. + +### Aucun message d'une venue + +- Vérifiez que le tunnel est actif : `sudo docker exec -it doublezero-edge-connect doublezero status` +- Vérifiez les erreurs de jointure dans les logs : `sudo docker logs -f doublezero-edge-connect` +- Confirmez que votre accès est autorisé pour cette venue onchain. +- Restreignez l'ingestion à cette venue avec `DZ_FEEDS=` pour isoler le problème. + +### Le WebSocket se connecte mais aucune cotation n'arrive + +- Les messages `instrument` arrivent toujours en premier ; les cotations suivent une fois le handshake de données de référence terminé. Attendez 10–20 secondes après la connexion avant de conclure que les données sont manquantes. +- Vérifiez `dz_feed_up{venue}` dans les métriques — `0` signifie que le multicast est silencieux sur votre hôte. +- Vérifiez que les règles de pare-feu autorisent le multicast UDP sur l'interface `doublezero1`. + +### `dz_ws_client_lagged_total` élevé + +Votre consommateur lit plus lentement que le pont ne publie. Augmentez le tampon de diffusion avec `WS_BROADCAST_CAPACITY`, réduisez le temps de traitement par message, ou ajoutez un thread de lecture dédié. + +### Le conteneur se ferme immédiatement + +- Le pont nécessite `--network host` et le périphérique `/dev/net/tun` ; un simple `docker run` sans ces flags échouera. +- Utilisez la commande d'installation en une ligne ou la commande `docker run` exacte indiquée dans [Auto-hébergement](#avance-auto-hebergement). + +### Le tunnel GRE ne s'établit pas + +Consultez [Dépannage](troubleshooting.md) et assurez-vous que le protocole IP 47 est autorisé chez votre fournisseur cloud. Sur AWS, désactivez la vérification source/dest de l'ENI pour l'hôte. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.it.md b/docs/Edge Market Data Connection.it.md new file mode 100644 index 0000000..e26c6ce --- /dev/null +++ b/docs/Edge Market Data Connection.it.md @@ -0,0 +1,465 @@ +--- +description: Esegui doublezero-edge-connect per ri-inoltrare gli shred di Solana verso una porta UDP locale e consumare dati di mercato Edge normalizzati tramite un WebSocket locale. +--- + +# Connessione Edge + +!!! warning "Connettendomi a DoubleZero accetto i [Termini di utilizzo di DoubleZero](https://doublezero.xyz/terms-protocol). I dati sono esclusivamente per uso interno e non possono essere ritrasmessi (vedi Sezione 2(e))." + +`doublezero-edge-connect` è un bridge che si connette al **multicast binario di DoubleZero Edge** e lo ri-serve localmente come due feed: + +1. **Inoltro degli shred Solana** — shred deduplicati (opzionalmente con verifica della firma) distribuiti a una o più destinazioni UDP locali, pronti per il tuo validator o RPC. +2. **Dati di mercato normalizzati** — feed dei venue Edge decodificati, corretti in precisione e ri-serviti come singolo WebSocket JSON su `ws://host:8081`. + +Entrambi vengono eseguiti dallo stesso container e dalla stessa installazione con un singolo comando. Abilita i feed consentiti dalla tua autorizzazione onchain. + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## Requisiti + +- Host **Linux/amd64** con un indirizzo IPv4 pubblico autorizzato onchain per l'ambiente target. +- **Docker** (il comando one-liner lo installa se mancante). +- **Connettività GRE** — consenti il protocollo IP 47 presso il tuo cloud provider; su AWS disabilita il controllo source/dest dell'ENI. +- Un **secret di accesso DoubleZero**: un token base64 con prefisso `DZ_` oppure un percorso a un file keypair, ottenuto dal processo di [onboarding DoubleZero](setup.md). + +--- + +## Passo 1: Installazione ed Esecuzione + +Un singolo comando prepara l'host e avvia il container bridge. Si connette alla rete DoubleZero e avvia ogni feed consentito dalla tua autorizzazione — inoltro shred e/o il WebSocket per i dati di mercato sulla porta `:8081`: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (privata)" + + ```bash + # Richiede un token GHCR con read:packages + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +Cosa fa lo script: + +1. Verifica che l'host sia Linux/amd64, assicura che Docker sia presente (propone l'installazione se assente). +2. Prepara il kernel dell'host per il tunnel GRE: carica `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avvisa riguardo alle regole del firewall e del cloud provider. +3. Carica il tuo secret di accesso (richiesto una sola volta se `DZ_SECRET` non è impostato). +4. Esegue il container bridge (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) e lancia `doublezero connect multicast`. + +!!! tip "Installazione non interattiva" + Imposta `DZ_SECRET=DZ_…` prima del pipe per eseguire in modo completamente automatico — nessun prompt. + +--- + +## Passo 2: Configurazione + +Tutta la configurazione avviene tramite **variabili d'ambiente impostate prima del pipe**. Non esiste un file di configurazione. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### Variabili dell'installer + +| Variabile | Default | Scopo | +|-----------|---------|-------| +| `DZ_SECRET` | *(richiesto interattivamente)* | Token base64 con prefisso `DZ_` **oppure** percorso a un file keypair. Un token viene iniettato nel container e non viene mai scritto su disco; un file viene montato in sola lettura tramite bind mount. | +| `DZ_ENV` | per script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | per script | Sovrascrive l'immagine del container. | +| `DZ_NAME` | `doublezero-edge-connect` | Nome del container. | +| `DZ_FEEDS` | *(tutti)* | Venue separati da virgola per restringere l'ingestione dei dati di mercato (es. `VenueA,VenueB`). Non influisce sull'inoltro degli shred Solana. | +| `DZ_ASSUME_YES` | `0` | Salta i prompt di conferma (es. il prompt di installazione Docker). | +| `DZ_GHCR_TOKEN` | — | **Solo Devnet** — un token GHCR con `read:packages` (l'immagine devnet è privata). | +| `DZ_GHCR_USER` | `malbeclabs` | **Solo Devnet** — username GHCR per il login. | + +### Variabili del bridge + +L'installer inoltra **qualsiasi** variabile bridge non vuota direttamente al container. Le più comuni: + +| Variabile | Default | Scopo | +|-----------|---------|-------| +| `DZ_IFACE` | `doublezero1` | Interfaccia di rete su cui mettersi in ascolto. | +| `DZ_RECV_BUF` | — | Override del buffer di ricezione UDP (in byte). | +| `METRICS_BIND` | *(vuoto / disattivato)* | Abilita l'endpoint Prometheus `/metrics` (es. `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | Livello di log (`debug`, `warn`, ecc.). | +| `DZ_SHRED_FORWARD` | — | Destinazione/i UDP locale/i per gli shred inoltrati — vedi [Inoltro Shred Solana](#inoltro-shred-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Indirizzo di bind del WebSocket per i dati di mercato — vedi [WebSocket Dati di Mercato](#websocket-dati-di-mercato). | +| `WS_MAX_CLIENTS` | `64` | Numero massimo di client WebSocket simultanei. | +| `WS_INPUT_COINS` | *(vuoto / disattivato)* | Abilita il backstop WebSocket pubblico per i simboli elencati (es. `BTC,ETH`). | + +**Esempi:** + +```bash +# Inoltra gli shred a un validator/RPC locale: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Non interattivo, testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# Restringi i dati di mercato a venue specifici, logging verboso, porta WS non predefinita: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Abilita le metriche e un backstop WS pubblico: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + Poiché l'installer inoltra solo valori **non vuoti**, non è possibile passare un override vuoto (es. `WS_BIND=""` per disabilitare il sink WebSocket) tramite il one-liner. Usa un `docker run` scritto manualmente per questo — vedi [Self-hosting](#avanzato-self-hosting). + +--- + +## Inoltro Shred Solana + +Il bridge si unisce ai gruppi multicast `edge-solana-*` per gli shred e inoltra ogni datagramma a una o più destinazioni UDP locali — alimentando direttamente il tuo validator o RPC dalla rete Edge. Si attiva automaticamente al discovery quando quei gruppi sono presenti nella tua autorizzazione. + +```bash +# Default (solo dedup, inoltro alla porta locale 20000): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Con verifica della firma: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| Variabile | Default | Scopo | +|-----------|---------|-------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destinazione/i per gli shred inoltrati (ripetibile). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (una copia per shred), `sigverify` (+ verifica ed25519), `none` (tutti i datagrammi). | +| `DZ_SHRED_RPC_URL` | — | Endpoint Solana RPC; richiesto dalla modalità `sigverify`. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Dimensione della finestra di deduplicazione. | + +Vedi [Inoltro shred](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) per la pipeline completa e le avvertenze. + +--- + +## WebSocket Dati di Mercato + +Apri un WebSocket verso `ws://:8081` e leggi frame JSON. Ricevi tutti i venue per cui sei autorizzato. Un messaggio opzionale `subscribe` restringe lo stream a venue e simboli specifici. + +Qualsiasi engine che parli WebSocket + JSON può consumarlo con un adapter leggero (~50–100 righe). Il multicast binario, la suddivisione a due porte per venue e l'handshake manifest/precisione restano tutti all'interno del bridge; l'unico contratto su cui il consumer deve implementare è il WebSocket JSON. + +### Ciclo di vita della connessione + +Ad ogni nuova connessione il bridge: + +1. **Riproduce le definizioni degli strumenti correnti** — un messaggio `instrument` per ogni simbolo noto — così il consumer ha le informazioni di precisione prima della prima quotazione. +2. **Riproduce l'ultimo snapshot di profondità** per simbolo (se il feed Market-by-Order è attivo). +3. **Invia in streaming** messaggi `quote` / `trade` / `midpoint` / `depth` man mano che arrivano, distribuiti a tutti i consumer connessi. + +``` +connect → instrument (×N) → depth (×M, ultimi book) → quote → trade → depth → … +``` + +### Tipi di messaggio + +Ogni messaggio è un oggetto JSON identificato da un campo `type`: + +| `type` | Significato | +|--------|-------------| +| `instrument` | Definizione dello strumento/precisione. | +| `quote` | Aggiornamento top-of-book (stato completo). | +| `trade` | Stampa di trade (ultimo scambio). | +| `midpoint` | Prezzo medio derivato. | +| `depth` | Snapshot completo della profondità dell'order book. | +| `status` | Transizione dello stato di salute del feed a livello di venue. | + +I consumer **devono ignorare valori `type` sconosciuti e campi sconosciuti** (compatibilità in avanti). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +Inviato alla connessione e ogni volta che le definizioni cambiano. `price_exponent` e `qty_exponent` indicano il tick size e lo step di dimensione del venue come potenze di dieci. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +Ogni `quote` è **stato completo** — un messaggio perso si auto-ripristina con la quotazione successiva, nessuna risincronizzazione necessaria. I quattro timestamp decompongono la latenza end-to-end: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (ricezione consumer) + book del venue arrivo sul wire post-decode hand-off WS +``` + +`0` è il valore sentinella per "non disponibile" — trattarlo come mancante, non come 1970. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` è `"buy"`, `"sell"` oppure `"unknown"`. I trade sono eventi puntuali e non vengono riprodotti alla riconnessione. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +I `bids` sono ordinati dal prezzo più alto al più basso; gli `asks` dal prezzo più basso al più alto. Ogni `depth` è uno **snapshot completo** — sostituire, non unire. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +Emesso all'edge quando il multicast delle quotazioni di un venue diventa silenzioso (`state:"down"`) o si riprende (`state:"ok"`). Usalo per disattivare visualmente un venue nella tua UI. La consegna delle quotazioni non è vincolata allo stato — il feed si auto-ripristina con la quotazione successiva. + +### Sottoscrizioni + +Per impostazione predefinita ricevi tutto. Invia un messaggio di controllo per restringere lo stream: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Omettere un campo corrisponde a qualsiasi valore (`{"symbol":"SOL"}` = SOL su ogni venue). `venue` viene confrontato senza distinzione tra maiuscole e minuscole. + +**Risposta di conferma del server:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Gli errori restituiscono `{"channel":"error","error":""}`. + +### Heartbeat e liveness + +- Il server invia un **WebSocket Ping** ogni 20 secondi; i client conformi rispondono automaticamente con Pong. +- I client silenziosi per 60 secondi vengono chiusi e rimossi. +- Keepalive a livello applicativo: `{"method":"ping"}` → `{"channel":"pong"}`. + +### Scheletro del consumer + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # tipi sconosciuti: ignorare silenziosamente (compatibilità in avanti) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### Sorgenti di input e backstop WebSocket + +Il feed multicast Edge è sempre attivo. Un **backstop WebSocket pubblico** opzionale può colmare le lacune quando il feed Edge si blocca: + +```bash +# Abilita il backstop per BTC ed ETH: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Le due sorgenti competono per tick `(venue, symbol, source_ts)` all'interno di un arbitro condiviso. In condizioni normali la sorgente Edge vince (sub-ms vs. decine di ms su internet); quando l'Edge presenta lacune, la copia pubblica interviene. L'output WebSocket è identico indipendentemente dalla sorgente che ha consegnato un determinato aggiornamento. + +--- + +## Gestione del Container + +```bash +# Streaming dei log +sudo docker logs -f doublezero-edge-connect + +# Verifica stato del tunnel +sudo docker exec -it doublezero-edge-connect doublezero status + +# Verifica latenze del dispositivo +sudo docker exec -it doublezero-edge-connect doublezero latency + +# Stop e rimozione +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "Nessun TLS" + Il bridge è progettato per una rete trusted/locale. Termina TLS con un reverse proxy se esponi l'endpoint WebSocket esternamente. + +--- + +## Monitoraggio (Metriche Prometheus) + +L'endpoint delle metriche è **disattivato per impostazione predefinita**. Abilitalo con `METRICS_BIND`: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Poi esegui lo scraping: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +Metriche principali: + +| Metrica | Cosa monitora | +|---------|---------------| +| `dz_feed_up{venue}` | `1` mentre il multicast del venue è attivo, `0` mentre è silenzioso. | +| `dz_datagrams_received_total{venue}` | Volume di ingestione per venue. | +| `dz_emit_total{venue,kind}` | Messaggi trasmessi dopo la deduplicazione, per tipo. | +| `dz_quotes_dropped_total{venue}` | Quotazioni obsolete/duplicate soppresse. | +| `dz_ws_clients` | Client WebSocket attualmente connessi. | +| `dz_ws_messages_sent_total{kind}` | Messaggi inoltrati ai client. | +| `dz_ws_client_lagged_total` | Volte in cui un client lento è stato disconnesso per proteggere il feed. | + +Una sonda di liveness `GET /healthz` è anch'essa servita sullo stesso indirizzo di bind. + +--- + +## Avanzato: Self-hosting + +Il container è disponibile su GHCR: + +| Ambiente | Immagine | Tag | +|----------|----------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (privata) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +Eseguilo manualmente (necessario per opzioni che l'installer non può inoltrare, come `WS_BIND=""`): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**Compilazione dal sorgente:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +Un buffer di ricezione del kernel più grande è consigliato per feed a raffica: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## Limiti e Backpressure + +| Limite | Default | Comportamento al superamento | +|--------|---------|------------------------------| +| Client simultanei (`WS_MAX_CLIENTS`) | 64 | La nuova connessione viene rifiutata. | +| Sottoscrizioni per client (`WS_MAX_SUBS`) | 256 | La `subscribe` viene rifiutata con un errore. | +| Messaggi di controllo in ingresso / client / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Il client viene disconnesso. | +| Buffer di broadcast (`WS_BROADCAST_CAPACITY`) | 4096 | Un client lento **perde i messaggi più vecchi** (non blocca mai il feed). | + +Poiché ogni `quote` e `depth` è stato completo, un consumer che perde messaggi sotto backpressure si auto-ripristina con il messaggio successivo — nessun handshake di risincronizzazione necessario. + +--- + +## Risoluzione dei Problemi + +### Nessuno shred in arrivo sulla porta locale + +- Conferma che il tuo accesso sia autorizzato per i gruppi shred `edge-solana-*` onchain. +- Verifica che il tunnel sia attivo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Controlla i log per errori di join: `sudo docker logs -f doublezero-edge-connect` +- Conferma che `DZ_SHRED_FORWARD` punti a una destinazione UDP locale raggiungibile. + +### Nessun messaggio da un venue + +- Verifica che il tunnel sia attivo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Controlla i log per errori di join: `sudo docker logs -f doublezero-edge-connect` +- Conferma che il tuo accesso sia autorizzato per quel venue onchain. +- Restringi l'ingestione a quel venue con `DZ_FEEDS=` per isolare il problema. + +### Il WebSocket si connette ma non arrivano quotazioni + +- I messaggi `instrument` arrivano sempre per primi; le quotazioni seguono una volta completato l'handshake dei dati di riferimento. Attendi 10–20 secondi dopo la connessione prima di concludere che i dati mancano. +- Controlla `dz_feed_up{venue}` nelle metriche — `0` significa che il multicast è silenzioso sul tuo host. +- Verifica che le regole del firewall consentano UDP multicast sull'interfaccia `doublezero1`. + +### `dz_ws_client_lagged_total` elevato + +Il tuo consumer sta leggendo più lentamente di quanto il bridge stia pubblicando. Aumenta il buffer di broadcast con `WS_BROADCAST_CAPACITY`, riduci il tempo di elaborazione per messaggio, oppure aggiungi un thread di lettura dedicato. + +### Il container termina immediatamente + +- Il bridge richiede `--network host` e il device `/dev/net/tun`; un semplice `docker run` senza questi flag fallirà. +- Usa il one-liner dell'installer o l'esatto comando `docker run` mostrato in [Self-hosting](#avanzato-self-hosting). + +### Il tunnel GRE non si stabilisce + +Fai riferimento a [Risoluzione dei problemi](troubleshooting.md) e assicurati che il protocollo IP 47 sia consentito presso il tuo cloud provider. Su AWS, disabilita il controllo source/dest dell'ENI per l'host. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.ja.md b/docs/Edge Market Data Connection.ja.md new file mode 100644 index 0000000..ed65f7e --- /dev/null +++ b/docs/Edge Market Data Connection.ja.md @@ -0,0 +1,463 @@ +--- +description: doublezero-edge-connect を実行して Solana shred をローカル UDP ポートに再転送し、正規化された Edge マーケットデータをローカル WebSocket 経由で受信します。 +--- + +# Edge 接続 + +!!! warning "DoubleZero に接続することで、[DoubleZero 利用規約](https://doublezero.xyz/terms-protocol)に同意したものとみなされます。データは内部目的でのみ使用可能であり、再送信は禁止されています(セクション 2(e) を参照)。" + +`doublezero-edge-connect` は **DoubleZero Edge バイナリマルチキャスト** に参加し、以下の 2 つのフィードとしてローカルに再配信するブリッジです: + +1. **Solana shred 転送** — 重複排除済み(オプションで署名検証済み)の shred を 1 つ以上のローカル UDP 宛先にファンアウトし、バリデーターまたは RPC に直接配信します。 +2. **正規化マーケットデータ** — Edge 取引所フィードをデコード・精度補正し、`ws://host:8081` 上の単一 JSON WebSocket として再配信します。 + +どちらも同じコンテナと同じワンライナーインストールで動作します。オンチェーン認可で許可されたフィードを有効にしてください。 + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## 要件 + +- ターゲット環境向けにオンチェーンで認可されたパブリック IPv4 アドレスを持つ **Linux/amd64** ホスト。 +- **Docker**(ワンライナーが未インストールの場合は自動インストールします)。 +- **GRE 接続** — クラウドプロバイダーで IP プロトコル 47 を許可してください。AWS では ENI のソース/宛先チェックを無効にしてください。 +- **DoubleZero アクセスシークレット**: `DZ_` プレフィックス付き base64 トークン、またはキーペアファイルのパス。[DoubleZero オンボーディング](setup.md)プロセスから取得します。 + +--- + +## ステップ 1: インストールと実行 + +1 つのコマンドでホストを準備し、ブリッジコンテナを起動します。DoubleZero ネットワークに参加し、認可で許可されたすべてのフィード(shred 転送および/または `:8081` のマーケットデータ WebSocket)を開始します: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (プライベート)" + + ```bash + # read:packages 権限を持つ GHCR トークンが必要です + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +スクリプトが実行する内容: + +1. ホストが Linux/amd64 であることを確認し、Docker の存在を確認します(未インストールの場合はインストールを提案)。 +2. GRE トンネル用にホストカーネルを準備します:`tun`/`ip_gre` をロードし、`net.core.rmem_max` を引き上げ、ファイアウォールとクラウドプロバイダーのルールについて警告します。 +3. アクセスシークレットを読み込みます(`DZ_SECRET` が未設定の場合は一度だけプロンプト表示)。 +4. ブリッジコンテナ(`--network host`、`NET_ADMIN`/`NET_RAW`、`/dev/net/tun`)を実行し、`doublezero connect multicast` を実行します。 + +!!! tip "非対話式インストール" + パイプの前に `DZ_SECRET=DZ_…` を設定すると、プロンプトなしで完全に無人実行できます。 + +--- + +## ステップ 2: 設定 + +すべての設定は**パイプの前に設定する環境変数**で行います。設定ファイルはありません。 + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### インストーラー変数 + +| 変数 | デフォルト | 用途 | +|----------|---------|---------| +| `DZ_SECRET` | *(プロンプト表示)* | `DZ_` プレフィックス付き base64 トークン**または**キーペアファイルのパス。トークンはコンテナに注入されディスクには書き込まれません。ファイルは読み取り専用でバインドマウントされます。 | +| `DZ_ENV` | スクリプトごと | `mainnet-beta` \| `testnet` \| `devnet`。 | +| `DZ_IMAGE` | スクリプトごと | コンテナイメージのオーバーライド。 | +| `DZ_NAME` | `doublezero-edge-connect` | コンテナ名。 | +| `DZ_FEEDS` | *(すべて)* | マーケットデータ取り込みを絞り込むカンマ区切りの取引所名(例: `VenueA,VenueB`)。Solana shred 転送には影響しません。 | +| `DZ_ASSUME_YES` | `0` | 確認プロンプトをスキップします(例: Docker インストールプロンプト)。 | +| `DZ_GHCR_TOKEN` | — | **Devnet 限定** — `read:packages` 権限を持つ GHCR トークン(devnet イメージはプライベートです)。 | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet 限定** — ログイン用の GHCR ユーザー名。 | + +### ブリッジ変数 + +インストーラーは**空でない**ブリッジ変数をすべてコンテナにそのまま転送します。主要なものは以下の通りです: + +| 変数 | デフォルト | 用途 | +|----------|---------|---------| +| `DZ_IFACE` | `doublezero1` | リッスンするネットワークインターフェース。 | +| `DZ_RECV_BUF` | — | UDP 受信バッファのオーバーライド(バイト)。 | +| `METRICS_BIND` | *(空 / 無効)* | Prometheus `/metrics` エンドポイントを有効にします(例: `127.0.0.1:9090`)。 | +| `RUST_LOG` | `info` | ログレベル(`debug`、`warn` など)。 | +| `DZ_SHRED_FORWARD` | — | 転送 shred のローカル UDP 宛先 — [Solana Shred 転送](#solana-shred-転送)を参照。 | +| `WS_BIND` | `0.0.0.0:8081` | マーケットデータ WebSocket のバインドアドレス — [マーケットデータ WebSocket](#マーケットデータ-websocket) を参照。 | +| `WS_MAX_CLIENTS` | `64` | WebSocket の最大同時接続クライアント数。 | +| `WS_INPUT_COINS` | *(空 / 無効)* | 指定シンボルのパブリック WebSocket バックストップを有効にします(例: `BTC,ETH`)。 | + +**例:** + +```bash +# ローカルのバリデーター/RPC に shred を転送: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# 非対話式、testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# 特定の取引所に絞り込み、詳細ログ、デフォルト以外の WS ポート: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# メトリクスとパブリック WS バックストップを有効化: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + インストーラーは**空でない**値のみを転送するため、ワンライナーで空のオーバーライド(例: WebSocket シンクを無効にする `WS_BIND=""`)を渡すことはできません。その場合は手動の `docker run` を使用してください — [セルフホスティング](#上級-セルフホスティング)を参照。 + +--- + +## Solana Shred 転送 + +ブリッジは `edge-solana-*` shred マルチキャストグループに参加し、各データグラムを 1 つ以上のローカル UDP 宛先にファンアウトします。Edge ネットワークからバリデーターまたは RPC に直接フィードします。認可にこれらのグループが含まれている場合、検出時に自動的にアクティブになります。 + +```bash +# デフォルト(重複排除のみ、ローカルポート 20000 に転送): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# 署名検証付き: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| 変数 | デフォルト | 用途 | +|----------|---------|---------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 転送 shred の宛先(繰り返し指定可能)。 | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(shred ごとに 1 コピー)、`sigverify`(+ ed25519 検証)、`none`(全データグラム)。 | +| `DZ_SHRED_RPC_URL` | — | Solana RPC エンドポイント。`sigverify` モードで必須。 | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 重複排除ウィンドウのサイズ。 | + +完全なパイプラインと注意事項については [Shred forwarding](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) を参照してください。 + +--- + +## マーケットデータ WebSocket + +`ws://:8081` に WebSocket 接続を開き、JSON フレームを読み取ります。認可された取引所のデータをすべて受信します。オプションの `subscribe` メッセージでストリームを特定の取引所やシンボルに絞り込めます。 + +WebSocket + JSON に対応する任意のエンジンが、薄い(〜50–100 行の)アダプターで消費できます。バイナリマルチキャスト、取引所ごとの 2 ポート分割、マニフェスト/精度ハンドシェイクはすべてブリッジ内に留まります。コンシューマーがコーディングする唯一の契約は WebSocket JSON です。 + +### 接続ライフサイクル + +新しい接続ごとにブリッジは以下を行います: + +1. **現在のインストゥルメント定義をリプレイ** — 既知のシンボルごとに 1 つの `instrument` メッセージ — コンシューマーが最初の気配値の前に精度情報を得られるようにします。 +2. **シンボルごとの最新板情報スナップショットをリプレイ**(Market-by-Order フィードがアクティブな場合)。 +3. `quote` / `trade` / `midpoint` / `depth` メッセージが到着次第、接続済みのすべてのコンシューマーに**ストリーミング**します。 + +``` +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +``` + +### メッセージタイプ + +すべてのメッセージは `type` フィールドでタグ付けされた JSON オブジェクトです: + +| `type` | 意味 | +|--------|---------| +| `instrument` | インストゥルメント/精度定義。 | +| `quote` | 最良気配値の更新(フルステート)。 | +| `trade` | 約定プリント(直近の取引)。 | +| `midpoint` | 算出されたミッド価格。 | +| `depth` | フルオーダーブック板情報スナップショット。 | +| `status` | 取引所レベルのフィードヘルス遷移。 | + +コンシューマーは**未知の `type` 値および未知のフィールドを無視しなければなりません**(前方互換性)。 + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +接続時および定義変更時に送信されます。`price_exponent` と `qty_exponent` は取引所のティックサイズとサイズステップを 10 のべき乗で表します。 + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +すべての `quote` は**フルステート**です — メッセージが欠落しても次の quote で自動復旧し、再同期は不要です。4 つのタイムスタンプでエンドツーエンドのレイテンシを分解できます: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off +``` + +`0` は「利用不可」のセンチネル値です — 1970 年ではなく欠損として扱ってください。 + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` は `"buy"`、`"sell"`、または `"unknown"` です。約定はポイントインタイムのイベントであり、再接続時にはリプレイされません。 + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`bids` は価格の高い順にソート、`asks` は価格の低い順にソートされています。各 `depth` は**フルスナップショット**です — マージではなく置換してください。 + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +取引所の quote マルチキャストが沈黙(`state:"down"`)または回復(`state:"ok"`)した際に Edge 上で発行されます。UI で取引所をグレーアウト表示するのに使用してください。Quote の配信は status に依存しません — フィードは次の quote で自動復旧します。 + +### サブスクリプション + +デフォルトではすべてを受信します。ストリームを絞り込むにはコントロールメッセージを送信します: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +フィールドを省略すると任意の値にマッチします(`{"symbol":"SOL"}` = すべての取引所の SOL)。`venue` は大文字小文字を区別しません。 + +**サーバー応答:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +エラーは `{"channel":"error","error":""}` を返します。 + +### ハートビートと生存確認 + +- サーバーは 20 秒ごとに **WebSocket Ping** を送信します。準拠クライアントは自動で Pong を返します。 +- 60 秒間無応答のクライアントは切断・解放されます。 +- アプリケーションレベルのキープアライブ:`{"method":"ping"}` → `{"channel":"pong"}`。 + +### コンシューマーのスケルトン + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # unknown types: silently ignore (forward compatibility) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### 入力ソースと WebSocket バックストップ + +Edge マルチキャストフィードは常時オンです。オプションの**パブリック WebSocket バックストップ**は、Edge フィードが停止した際にギャップを補います: + +```bash +# BTC と ETH のバックストップを有効化: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +2 つのソースは共有アービターの中で `(venue, symbol, source_ts)` ティックごとに競争します。定常状態では Edge ソースが勝利します(インターネット経由の数十 ms に対してサブ ms)。Edge にギャップが生じた場合、パブリックコピーが補完します。どのソースが更新を配信したかに関わらず、WebSocket 出力は同一です。 + +--- + +## コンテナの管理 + +```bash +# ログをストリーミング +sudo docker logs -f doublezero-edge-connect + +# トンネル状態を確認 +sudo docker exec -it doublezero-edge-connect doublezero status + +# デバイスレイテンシを確認 +sudo docker exec -it doublezero-edge-connect doublezero latency + +# 停止と削除 +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "TLS なし" + ブリッジは信頼済み/ローカルネットワークを対象としています。WebSocket エンドポイントを外部に公開する場合は、リバースプロキシで TLS を終端してください。 + +--- + +## モニタリング(Prometheus メトリクス) + +メトリクスエンドポイントは**デフォルトで無効**です。`METRICS_BIND` で有効にします: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +スクレイプ: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +主要メトリクス: + +| メトリクス | 追跡内容 | +|--------|---------------| +| `dz_feed_up{venue}` | 取引所のマルチキャストがライブの間 `1`、沈黙中は `0`。 | +| `dz_datagrams_received_total{venue}` | 取引所ごとの取り込み量。 | +| `dz_emit_total{venue,kind}` | 重複排除後にブロードキャストされたメッセージ(タイプ別)。 | +| `dz_quotes_dropped_total{venue}` | 抑制された古い/重複 quote。 | +| `dz_ws_clients` | 現在接続中の WebSocket クライアント数。 | +| `dz_ws_messages_sent_total{kind}` | クライアントに転送されたメッセージ。 | +| `dz_ws_client_lagged_total` | フィードを保護するために遅延クライアントが切断された回数。 | + +`GET /healthz` 生存確認プローブも同じバインドアドレスで提供されます。 + +--- + +## 上級: セルフホスティング + +コンテナは GHCR で利用可能です: + +| 環境 | イメージ | タグ | +|-------------|-------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (プライベート) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +手動で実行します(インストーラーが転送できないオプション、例えば `WS_BIND=""` が必要な場合): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**ソースからビルド:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +バースト性の高いフィードには、より大きなカーネル受信バッファを推奨します: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## 制限とバックプレッシャー + +| 制限 | デフォルト | 超過時の動作 | +|-------|---------|------------------------| +| 同時接続クライアント数 (`WS_MAX_CLIENTS`) | 64 | 新しい接続が拒否されます。 | +| クライアントごとのサブスクリプション数 (`WS_MAX_SUBS`) | 256 | `subscribe` がエラーで拒否されます。 | +| クライアントごとの受信制御メッセージ数/分 (`WS_MAX_INBOUND_PER_MIN`) | 600 | クライアントが切断されます。 | +| ブロードキャストバッファ (`WS_BROADCAST_CAPACITY`) | 4096 | 遅延クライアントは**最も古いメッセージをドロップ**します(フィードを停止させることはありません)。 | + +すべての `quote` と `depth` はフルステートであるため、バックプレッシャーでメッセージをドロップしたコンシューマーは次のメッセージで自動復旧します — 再同期ハンドシェイクは不要です。 + +--- + +## トラブルシューティング + +### ローカルポートに shred が到着しない + +- オンチェーンで `edge-solana-*` shred グループへのアクセスが認可されていることを確認してください。 +- トンネルが稼働中か確認してください:`sudo docker exec -it doublezero-edge-connect doublezero status` +- 参加エラーのログを確認してください:`sudo docker logs -f doublezero-edge-connect` +- `DZ_SHRED_FORWARD` が到達可能なローカル UDP 宛先を指していることを確認してください。 + +### 取引所からメッセージが来ない + +- トンネルが稼働中か確認してください:`sudo docker exec -it doublezero-edge-connect doublezero status` +- 参加エラーのログを確認してください:`sudo docker logs -f doublezero-edge-connect` +- オンチェーンでその取引所へのアクセスが認可されていることを確認してください。 +- 問題を切り分けるため、`DZ_FEEDS=` でその取引所に取り込みを絞り込んでください。 + +### WebSocket は接続するが quote が到着しない + +- `instrument` メッセージが常に最初に到着します。リファレンスデータのハンドシェイクが完了すると quote が続きます。データが欠落していると判断する前に、接続後 10〜20 秒待ってください。 +- メトリクスの `dz_feed_up{venue}` を確認してください — `0` はホスト上でマルチキャストが沈黙していることを意味します。 +- ファイアウォールルールが `doublezero1` インターフェース上のマルチキャスト UDP を許可していることを確認してください。 + +### `dz_ws_client_lagged_total` が高い + +コンシューマーの読み取り速度がブリッジの配信速度より遅くなっています。`WS_BROADCAST_CAPACITY` でブロードキャストバッファを増やすか、メッセージごとの処理時間を短縮するか、専用のリーダースレッドを追加してください。 + +### コンテナが即座に終了する + +- ブリッジには `--network host` と `/dev/net/tun` デバイスが必要です。これらのフラグなしの通常の `docker run` は失敗します。 +- インストーラーのワンライナーまたは[セルフホスティング](#上級-セルフホスティング)に記載されている正確な `docker run` コマンドを使用してください。 + +### GRE トンネルが確立されない \ No newline at end of file diff --git a/docs/Edge Market Data Connection.ko.md b/docs/Edge Market Data Connection.ko.md new file mode 100644 index 0000000..9c69325 --- /dev/null +++ b/docs/Edge Market Data Connection.ko.md @@ -0,0 +1,436 @@ +--- +description: doublezero-edge-connect를 실행하여 Solana 시레드(shred)를 로컬 UDP 포트로 재전달하고 정규화된 Edge 시장 데이터를 로컬 WebSocket을 통해 소비합니다. +--- + +# Edge 연결 + +!!! warning "DoubleZero에 연결함으로써 [DoubleZero 이용약관](https://doublezero.xyz/terms-protocol)에 동의합니다. 데이터는 내부 목적으로만 사용할 수 있으며 재전송할 수 없습니다(제2조(e) 참조)." + +`doublezero-edge-connect`는 **DoubleZero Edge 바이너리 멀티캐스트**에 참여하여 이를 로컬에서 두 가지 피드로 제공하는 브리지입니다: + +1. **Solana 시레드 포워딩** — 중복 제거된(선택적으로 서명 검증된) 시레드를 하나 이상의 로컬 UDP 대상으로 팬아웃하여 밸리데이터 또는 RPC에서 바로 사용할 수 있습니다. +2. **정규화된 시장 데이터** — Edge 거래소 피드를 디코딩하고 정밀도를 보정한 후 `ws://host:8081`에서 단일 JSON WebSocket으로 제공합니다. + +두 피드 모두 동일한 컨테이너와 동일한 원라인 설치로 실행됩니다. 온체인 인가가 부여한 피드를 활성화하세요. + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## 요구 사항 + +- 대상 환경에 대해 온체인으로 인가된 공인 IPv4 주소를 가진 **Linux/amd64** 호스트. +- **Docker** (원라이너가 없는 경우 설치합니다). +- **GRE 연결** — 클라우드 제공자에서 IP 프로토콜 47을 허용하세요. AWS에서는 ENI 소스/목적지 확인을 비활성화하세요. +- **DoubleZero 액세스 시크릿**: `DZ_` 접두사가 붙은 base64 토큰 또는 키페어 파일 경로로, [DoubleZero 온보딩](setup.md) 과정에서 발급받습니다. + +--- + +## 1단계: 설치 및 실행 + +하나의 명령으로 호스트를 준비하고 브리지 컨테이너를 시작합니다. DoubleZero 네트워크에 참여하고 인가가 부여한 모든 피드(시레드 포워딩 및/또는 `:8081`의 시장 데이터 WebSocket)를 시작합니다: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (비공개)" + + ```bash + # read:packages 권한이 있는 GHCR 토큰이 필요합니다 + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +스크립트가 수행하는 작업: + +1. 호스트가 Linux/amd64인지 확인하고, Docker가 있는지 확인합니다(없으면 설치를 제안합니다). +2. GRE 터널을 위해 호스트 커널을 준비합니다: `tun`/`ip_gre` 로드, `net.core.rmem_max` 증가, 방화벽 및 클라우드 제공자 규칙에 대한 경고. +3. 액세스 시크릿을 로드합니다(`DZ_SECRET`이 설정되지 않은 경우 한 번 프롬프트됩니다). +4. 브리지 컨테이너를 실행하고(`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) `doublezero connect multicast`를 실행합니다. + +!!! tip "비대화형 설치" + 파이프 앞에 `DZ_SECRET=DZ_…`를 설정하면 프롬프트 없이 완전히 무인으로 실행됩니다. + +--- + +## 2단계: 구성 + +모든 구성은 **파이프 앞에 설정하는 환경 변수**를 통해 이루어집니다. 구성 파일은 없습니다. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### 설치 프로그램 변수 + +| 변수 | 기본값 | 용도 | +|----------|---------|---------| +| `DZ_SECRET` | *(프롬프트)* | `DZ_` 접두사가 붙은 base64 토큰 **또는** 키페어 파일 경로. 토큰은 컨테이너에 주입되며 디스크에 기록되지 않습니다. 파일은 읽기 전용으로 바인드 마운트됩니다. | +| `DZ_ENV` | 스크립트별 | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | 스크립트별 | 컨테이너 이미지를 재정의합니다. | +| `DZ_NAME` | `doublezero-edge-connect` | 컨테이너 이름. | +| `DZ_FEEDS` | *(전체)* | 시장 데이터 수집을 좁히는 쉼표로 구분된 거래소 목록 (예: `VenueA,VenueB`). Solana 시레드 포워딩에는 영향을 미치지 않습니다. | +| `DZ_ASSUME_YES` | `0` | 확인 프롬프트를 건너뜁니다 (예: Docker 설치 프롬프트). | +| `DZ_GHCR_TOKEN` | — | **Devnet 전용** — `read:packages` 권한이 있는 GHCR 토큰(devnet 이미지는 비공개). | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet 전용** — 로그인을 위한 GHCR 사용자명. | + +### 브리지 변수 + +설치 프로그램은 **비어 있지 않은** 모든 브리지 변수를 컨테이너로 직접 전달합니다. 주요 변수: + +| 변수 | 기본값 | 용도 | +|----------|---------|---------| +| `DZ_IFACE` | `doublezero1` | 리스닝할 네트워크 인터페이스. | +| `DZ_RECV_BUF` | — | UDP 수신 버퍼 재정의 (바이트). | +| `METRICS_BIND` | *(비어 있음 / 비활성)* | Prometheus `/metrics` 엔드포인트 활성화 (예: `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | 로그 레벨 (`debug`, `warn` 등). | +| `DZ_SHRED_FORWARD` | — | 포워딩된 시레드의 로컬 UDP 대상 — [Solana 시레드 포워딩](#solana-시레드-포워딩) 참조. | +| `WS_BIND` | `0.0.0.0:8081` | 시장 데이터 WebSocket 바인드 주소 — [시장 데이터 WebSocket](#시장-데이터-websocket) 참조. | +| `WS_MAX_CLIENTS` | `64` | 최대 동시 WebSocket 클라이언트 수. | +| `WS_INPUT_COINS` | *(비어 있음 / 비활성)* | 나열된 심볼에 대한 공개 WebSocket 백스톱 활성화 (예: `BTC,ETH`). | + +**예시:** + +```bash +# 로컬 밸리데이터/RPC로 시레드 포워딩: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# 비대화형, testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# 특정 거래소로 시장 데이터 좁히기, 상세 로깅, 비기본 WS 포트: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# 메트릭 및 공개 WS 백스톱 활성화: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + 설치 프로그램은 **비어 있지 않은** 값만 전달하므로, 원라이너를 통해 빈 재정의(예: WebSocket 싱크를 비활성화하기 위한 `WS_BIND=""`)를 전달할 수 없습니다. 이 경우 수동으로 `docker run`을 작성하세요 — [자체 호스팅](#고급-자체-호스팅) 참조. + +--- + +## Solana 시레드 포워딩 + +브리지는 `edge-solana-*` 시레드 멀티캐스트 그룹에 참여하고 각 데이터그램을 하나 이상의 로컬 UDP 대상으로 팬아웃합니다 — Edge 네트워크에서 직접 밸리데이터 또는 RPC에 공급합니다. 인가에 해당 그룹이 포함되어 있으면 검색 시 자동으로 활성화됩니다. + +```bash +# 기본값 (중복 제거만, 로컬 포트 20000으로 포워딩): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# 서명 검증 포함: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| 변수 | 기본값 | 용도 | +|----------|---------|---------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 포워딩된 시레드의 대상 (반복 가능). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (시레드당 한 복사본), `sigverify` (+ ed25519 검증), `none` (모든 데이터그램). | +| `DZ_SHRED_RPC_URL` | — | Solana RPC 엔드포인트; `sigverify` 모드에 필요합니다. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 중복 제거 윈도우 크기. | + +전체 파이프라인과 주의사항은 [시레드 포워딩](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)을 참조하세요. + +--- + +## 시장 데이터 WebSocket + +`ws://:8081`에 WebSocket을 연결하고 JSON 프레임을 읽으세요. 인가된 모든 거래소의 데이터를 수신합니다. 선택적 `subscribe` 메시지를 통해 스트림을 특정 거래소와 심볼로 좁힐 수 있습니다. + +WebSocket + JSON을 지원하는 모든 엔진은 간단한 (~50–100줄) 어댑터로 이를 소비할 수 있습니다. 바이너리 멀티캐스트, 거래소별 2포트 분할, 매니페스트/정밀도 핸드셰이크는 모두 브리지 내부에서 처리됩니다. 소비자가 코딩해야 할 유일한 계약은 WebSocket JSON입니다. + +### 연결 라이프사이클 + +새 연결마다 브리지는: + +1. **현재 상품 정의를 재전송합니다** — 알려진 심볼당 하나의 `instrument` 메시지 — 소비자가 첫 번째 호가 전에 정밀도를 파악할 수 있도록 합니다. +2. **심볼당 최신 호가창 스냅샷을 재전송합니다** (Market-by-Order 피드가 활성인 경우). +3. `quote` / `trade` / `midpoint` / `depth` 메시지를 도착하는 대로 **스트리밍**하며 연결된 모든 소비자에게 팬아웃합니다. + +``` +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +``` + +### 메시지 유형 + +모든 메시지는 `type` 필드로 태그된 JSON 객체입니다: + +| `type` | 의미 | +|--------|---------| +| `instrument` | 상품/정밀도 정의. | +| `quote` | 최우선 호가 업데이트 (전체 상태). | +| `trade` | 체결 내역 (최근 거래). | +| `midpoint` | 파생 중간 가격. | +| `depth` | 전체 호가창 깊이 스냅샷. | +| `status` | 거래소 수준 피드 상태 전환. | + +소비자는 **알 수 없는 `type` 값과 알 수 없는 필드를 무시해야 합니다** (전방 호환성). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +연결 시와 정의가 변경될 때마다 전송됩니다. `price_exponent`와 `qty_exponent`는 거래소의 틱 사이즈와 수량 단위를 10의 거듭제곱으로 나타냅니다. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +모든 `quote`는 **전체 상태**입니다 — 메시지가 손실되어도 다음 호가에서 자동 복구되며, 재동기화가 필요 없습니다. 네 개의 타임스탬프는 종단 간 지연 시간을 분해합니다: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off +``` + +`0`은 "사용할 수 없음"을 나타내는 센티넬 값입니다 — 1970이 아닌 누락된 값으로 처리하세요. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side`는 `"buy"`, `"sell"` 또는 `"unknown"`입니다. 체결은 시점 이벤트이며 재연결 시 재전송되지 않습니다. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`bids`는 최고가 우선으로 정렬되고 `asks`는 최저가 우선으로 정렬됩니다. 각 `depth`는 **전체 스냅샷**입니다 — 병합하지 말고 교체하세요. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +거래소의 호가 멀티캐스트가 침묵할 때(`state:"down"`) 또는 복구될 때(`state:"ok"`) Edge에서 발생합니다. UI에서 거래소를 비활성 상태로 표시하는 데 사용하세요. 호가 전달은 상태에 의해 차단되지 않습니다 — 피드는 다음 호가에서 자동 복구됩니다. + +### 구독 + +기본적으로 모든 것을 수신합니다. 스트림을 좁히려면 제어 메시지를 보내세요: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +필드를 생략하면 모든 값과 일치합니다(`{"symbol":"SOL"}` = 모든 거래소의 SOL). `venue`는 대소문자를 구분하지 않고 매칭됩니다. + +**서버 확인 응답:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +오류는 `{"channel":"error","error":""}`으로 반환됩니다. + +### 하트비트 및 활성 확인 + +- 서버는 20초마다 **WebSocket Ping**을 보냅니다. 호환 클라이언트는 자동으로 Pong을 응답합니다. +- 60초 동안 비활성인 클라이언트는 닫히고 정리됩니다. +- 애플리케이션 수준 keepalive: `{"method":"ping"}` → `{"channel":"pong"}`. + +### 소비자 스켈레톤 + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # unknown types: silently ignore (forward compatibility) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### 입력 소스와 WebSocket 백스톱 + +Edge 멀티캐스트 피드는 항상 활성 상태입니다. 선택적 **공개 WebSocket 백스톱**은 Edge 피드가 중단될 때 격차를 메울 수 있습니다: + +```bash +# BTC와 ETH에 대한 백스톱 활성화: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +두 소스는 공유 아비터 내에서 `(venue, symbol, source_ts)` 틱 단위로 경쟁합니다. 정상 상태에서는 Edge 소스가 승리합니다(인터넷 경유 수십 ms 대비 1ms 미만). Edge에 격차가 발생하면 공개 복사본이 채웁니다. WebSocket 출력은 어떤 소스가 특정 업데이트를 전달했는지와 관계없이 동일합니다. + +--- + +## 컨테이너 관리 + +```bash +# 로그 스트리밍 +sudo docker logs -f doublezero-edge-connect + +# 터널 상태 확인 +sudo docker exec -it doublezero-edge-connect doublezero status + +# 디바이스 지연 시간 확인 +sudo docker exec -it doublezero-edge-connect doublezero latency + +# 중지 및 제거 +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "TLS 없음" + 브리지는 신뢰할 수 있는/로컬 네트워크를 대상으로 합니다. WebSocket 엔드포인트를 외부에 노출하는 경우 리버스 프록시에서 TLS를 종단하세요. + +--- + +## 모니터링 (Prometheus 메트릭) + +메트릭 엔드포인트는 **기본적으로 비활성**입니다. `METRICS_BIND`로 활성화하세요: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +그런 다음 스크래핑하세요: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +주요 메트릭: + +| 메트릭 | 추적 대상 | +|--------|---------------| +| `dz_feed_up{venue}` | 해당 거래소의 멀티캐스트가 활성이면 `1`, 침묵이면 `0`. | +| `dz_datagrams_received_total{venue}` | 거래소별 수집 볼륨. | +| `dz_emit_total{venue,kind}` | 중복 제거 후 유형별 브로드캐스트된 메시지. | +| `dz_quotes_dropped_total{venue}` | 억제된 오래된/중복 호가. | +| `dz_ws_clients` | 현재 연결된 WebSocket 클라이언트 수. | +| `dz_ws_messages_sent_total{kind}` | 클라이언트에 포워딩된 메시지. | +| `dz_ws_client_lagged_total` | 피드 보호를 위해 느린 클라이언트가 제거된 횟수. | + +동일한 바인드 주소에 `GET /healthz` 활성 프로브도 제공됩니다. + +--- + +## 고급: 자체 호스팅 + +컨테이너는 GHCR에서 사용할 수 있습니다: + +| 환경 | 이미지 | 태그 | +|-------------|-------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (비공개) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +수동으로 실행합니다(설치 프로그램이 전달할 수 없는 옵션, 예: `WS_BIND=""`에 필요): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**소스에서 빌드:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +버스트 피드에는 더 큰 커널 수신 버퍼가 권장됩니다: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## 제한 및 백프레셔 + +| 제한 | 기본값 | 초과 시 동작 | +|-------|---------|------------------------| +| 동시 클라이언트 수 (`WS_MAX_CLIENTS`) | 64 | 새 연결이 거부됩니다. | +| 클라이언트당 구독 수 (`WS_MAX_SUBS`) | 256 | `subscribe`가 오류와 함께 거부됩니다. | +| 클라이언트당 분당 인바운드 제어 메시지 수 (`WS_MAX_INBOUND_PER_MIN`) | 600 | 클라이언트 연결이 끊깁니다. | +| 브로드캐스트 버퍼 (`WS_BROADCAST_CAPACITY`) | 4096 | 느린 클라이언트는 **가장 오래된 메시지를 드롭합니다** (피드를 차단하지 않습니다). | + +모든 `quote`와 `depth`는 전체 상태이므로, 백프레셔로 인해 메시지를 드롭한 소비자도 다음 메시지에서 자동 복구됩니다 — 재동기화 핸드셰이크가 필요 없습니다. + +--- + +## 문제 해결 + +### 로컬 포트에 시레드가 도착하지 않음 + +- 온체인에서 `edge-solana-*` 시레드 그룹에 대한 접근이 인가 \ No newline at end of file diff --git a/docs/Edge Market Data Connection.pt.md b/docs/Edge Market Data Connection.pt.md new file mode 100644 index 0000000..75eb469 --- /dev/null +++ b/docs/Edge Market Data Connection.pt.md @@ -0,0 +1,465 @@ +--- +description: Execute o doublezero-edge-connect para reencaminhar shreds Solana para uma porta UDP local e consumir dados de mercado normalizados do Edge através de um WebSocket local. +--- + +# Conexão Edge + +!!! warning "Ao conectar-me ao DoubleZero, concordo com os [Termos de Uso do DoubleZero](https://doublezero.xyz/terms-protocol). Os dados são apenas para uso interno e não podem ser retransmitidos (consulte a Secção 2(e))." + +`doublezero-edge-connect` é uma ponte que se junta ao **multicast binário do DoubleZero Edge** e o re-serve localmente como dois feeds: + +1. **Encaminhamento de shreds Solana** — shreds deduplicados (opcionalmente com verificação de assinatura) distribuídos para um ou mais destinos UDP locais, prontos para o seu validador ou RPC. +2. **Dados de mercado normalizados** — feeds de venues Edge decodificados, com precisão corrigida, e re-servidos como um único WebSocket JSON em `ws://host:8081`. + +Ambos executam a partir do mesmo contêiner e da mesma instalação de uma linha. Ative os feeds que a sua autorização onchain conceder. + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## Requisitos + +- Host **Linux/amd64** com um endereço IPv4 público autorizado onchain para o ambiente alvo. +- **Docker** (o comando de uma linha instala-o se estiver ausente). +- **Conectividade GRE** — permita o protocolo IP 47 no seu provedor cloud; na AWS desative a verificação source/dest da ENI. +- Um **segredo de acesso DoubleZero**: um token base64 com prefixo `DZ_` ou um caminho para um ficheiro de keypair, obtido no processo de [onboarding DoubleZero](setup.md). + +--- + +## Passo 1: Instalar e Executar + +Um único comando prepara o host e inicia o contêiner da ponte. Ele junta-se à rede DoubleZero e inicia todos os feeds que a sua autorização conceder — encaminhamento de shreds e/ou o WebSocket de dados de mercado na porta `:8081`: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet (privada)" + + ```bash + # Requer um token GHCR com read:packages + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +O que o script faz: + +1. Verifica que o host é Linux/amd64, garante que o Docker está presente (oferece instalá-lo). +2. Prepara o kernel do host para o túnel GRE: carrega `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avisa sobre regras de firewall e do provedor cloud. +3. Carrega o seu segredo de acesso (solicitado uma vez se `DZ_SECRET` não estiver definido). +4. Executa o contêiner da ponte (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) e executa `doublezero connect multicast`. + +!!! tip "Instalação não interativa" + Defina `DZ_SECRET=DZ_…` antes do pipe para executar completamente sem supervisão — sem prompts. + +--- + +## Passo 2: Configurar + +Toda a configuração é feita via **variáveis de ambiente definidas antes do pipe**. Não existe ficheiro de configuração. + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### Variáveis do instalador + +| Variável | Padrão | Finalidade | +|----------|--------|------------| +| `DZ_SECRET` | *(solicitado)* | Token base64 com prefixo `DZ_` **ou** caminho para um ficheiro de keypair. Um token é injetado no contêiner e nunca escrito em disco; um ficheiro é montado em modo somente leitura. | +| `DZ_ENV` | por script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | por script | Substituir a imagem do contêiner. | +| `DZ_NAME` | `doublezero-edge-connect` | Nome do contêiner. | +| `DZ_FEEDS` | *(todos)* | Venues separadas por vírgula para restringir a ingestão de dados de mercado (ex.: `VenueA,VenueB`). Não afeta o encaminhamento de shreds Solana. | +| `DZ_ASSUME_YES` | `0` | Ignorar prompts de confirmação (ex.: o prompt de instalação do Docker). | +| `DZ_GHCR_TOKEN` | — | **Apenas Devnet** — um token GHCR com `read:packages` (a imagem devnet é privada). | +| `DZ_GHCR_USER` | `malbeclabs` | **Apenas Devnet** — nome de utilizador GHCR para o login. | + +### Variáveis da ponte + +O instalador encaminha **qualquer variável de ponte não vazia** diretamente para o contêiner. As mais comuns: + +| Variável | Padrão | Finalidade | +|----------|--------|------------| +| `DZ_IFACE` | `doublezero1` | Interface de rede para escutar. | +| `DZ_RECV_BUF` | — | Substituição do buffer de receção UDP (bytes). | +| `METRICS_BIND` | *(vazio / desativado)* | Ativar o endpoint Prometheus `/metrics` (ex.: `127.0.0.1:9090`). | +| `RUST_LOG` | `info` | Nível de log (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destino(s) UDP local(ais) para shreds encaminhados — veja [Encaminhamento de Shreds Solana](#encaminhamento-de-shreds-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Endereço de bind do WebSocket de dados de mercado — veja [WebSocket de Dados de Mercado](#websocket-de-dados-de-mercado). | +| `WS_MAX_CLIENTS` | `64` | Máximo de clientes WebSocket simultâneos. | +| `WS_INPUT_COINS` | *(vazio / desativado)* | Ativar o backstop público via WebSocket para os símbolos listados (ex.: `BTC,ETH`). | + +**Exemplos:** + +```bash +# Encaminhar shreds para um validador/RPC local: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Não interativo, testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# Restringir dados de mercado a venues específicas, logging verboso, porta WS não padrão: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# Ativar métricas e um backstop WS público: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + Como o instalador só encaminha valores **não vazios**, não é possível passar uma substituição vazia (ex.: `WS_BIND=""` para desativar o sink WebSocket) através do comando de uma linha. Use um `docker run` escrito manualmente para isso — veja [Self-hosting](#avancado-self-hosting). + +--- + +## Encaminhamento de Shreds Solana + +A ponte junta-se aos grupos multicast `edge-solana-*` de shreds e distribui cada datagrama para um ou mais destinos UDP locais — alimentando o seu validador ou RPC diretamente a partir da rede Edge. Ativa-se automaticamente na descoberta quando esses grupos estão presentes na sua autorização. + +```bash +# Padrão (apenas dedup, encaminhar para porta local 20000): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Com verificação de assinatura: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| Variável | Padrão | Finalidade | +|----------|--------|------------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destino(s) para shreds encaminhados (repetível). | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (uma cópia por shred), `sigverify` (+ verificação ed25519), `none` (todos os datagramas). | +| `DZ_SHRED_RPC_URL` | — | Endpoint RPC Solana; necessário pelo modo `sigverify`. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Tamanho da janela de deduplicação. | + +Veja [Encaminhamento de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para o pipeline completo e ressalvas. + +--- + +## WebSocket de Dados de Mercado + +Abra um WebSocket para `ws://:8081` e leia frames JSON. Recebe todas as venues para as quais está autorizado. Uma mensagem opcional `subscribe` restringe o fluxo a venues e símbolos específicos. + +Qualquer engine que fale WebSocket + JSON pode consumi-lo com um adaptador simples (~50–100 linhas). O multicast binário, a divisão em duas portas por venue, e o handshake de manifesto/precisão ficam todos dentro da ponte; o único contrato contra o qual um consumidor programa é o WebSocket JSON. + +### Ciclo de vida da conexão + +Em cada nova conexão, a ponte: + +1. **Repete as definições de instrumentos atuais** — uma mensagem `instrument` por símbolo conhecido — para que o consumidor tenha a precisão antes da primeira cotação. +2. **Repete o último snapshot de profundidade** por símbolo (se o feed Market-by-Order estiver ativo). +3. **Transmite** mensagens `quote` / `trade` / `midpoint` / `depth` à medida que chegam, distribuídas a todos os consumidores conectados. + +``` +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +``` + +### Tipos de mensagem + +Cada mensagem é um objeto JSON identificado por um campo `type`: + +| `type` | Significado | +|--------|-------------| +| `instrument` | Definição de instrumento/precisão. | +| `quote` | Atualização do topo do livro (estado completo). | +| `trade` | Impressão de negócio (última venda). | +| `midpoint` | Preço médio derivado. | +| `depth` | Snapshot completo de profundidade do livro de ordens. | +| `status` | Transição de saúde do feed a nível de venue. | + +Os consumidores **devem ignorar valores `type` desconhecidos e campos desconhecidos** (compatibilidade futura). + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +Enviado na conexão e sempre que as definições mudam. `price_exponent` e `qty_exponent` indicam o tick size e o step de tamanho da venue como potências de dez. + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +Cada `quote` é **estado completo** — uma mensagem perdida auto-recupera na próxima cotação, sem necessidade de ressincronização. Os quatro timestamps decompõem a latência de ponta a ponta: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off +``` + +`0` é o sentinela para "não disponível" — trate-o como ausente, não como 1970. + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` é `"buy"`, `"sell"` ou `"unknown"`. Negócios são eventos pontuais no tempo e não são repetidos na reconexão. + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`bids` são ordenados do preço mais alto para o mais baixo; `asks` são ordenados do preço mais baixo para o mais alto. Cada `depth` é um **snapshot completo** — substitua, não faça merge. + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +Emitido no edge quando o multicast de cotações de uma venue fica silencioso (`state:"down"`) ou recupera (`state:"ok"`). Use-o para desativar visualmente uma venue na sua UI. A entrega de cotações não depende do status — o feed auto-recupera na próxima cotação. + +### Subscrições + +Por padrão, recebe tudo. Envie uma mensagem de controlo para restringir o fluxo: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Omitir um campo corresponde a qualquer valor (`{"symbol":"SOL"}` = SOL em todas as venues). `venue` é comparado sem distinção de maiúsculas/minúsculas. + +**Confirmação do servidor:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +Erros retornam `{"channel":"error","error":""}`. + +### Heartbeat e liveness + +- O servidor envia um **WebSocket Ping** a cada 20 segundos; clientes conformes respondem automaticamente com Pong. +- Clientes silenciosos por 60 segundos são fechados e eliminados. +- Keepalive a nível de aplicação: `{"method":"ping"}` → `{"channel":"pong"}`. + +### Esqueleto do consumidor + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # unknown types: silently ignore (forward compatibility) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### Fontes de entrada e o backstop WebSocket + +O feed multicast Edge está sempre ativo. Um **backstop público via WebSocket** opcional pode preencher lacunas quando o feed Edge estagna: + +```bash +# Ativar o backstop para BTC e ETH: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +As duas fontes competem por tick `(venue, symbol, source_ts)` dentro de um árbitro partilhado. Em estado estável, a fonte Edge vence (sub-ms vs. dezenas de ms pela internet); quando o Edge falha, a cópia pública preenche. A saída WebSocket é idêntica independentemente de qual fonte entregou uma determinada atualização. + +--- + +## Gerir o Contêiner + +```bash +# Transmitir logs +sudo docker logs -f doublezero-edge-connect + +# Verificar o estado do túnel +sudo docker exec -it doublezero-edge-connect doublezero status + +# Verificar latências do dispositivo +sudo docker exec -it doublezero-edge-connect doublezero latency + +# Parar e remover +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "Sem TLS" + A ponte destina-se a uma rede confiável/local. Termine o TLS num reverse proxy se expuser o endpoint WebSocket externamente. + +--- + +## Monitorização (Métricas Prometheus) + +O endpoint de métricas está **desativado por padrão**. Ative-o com `METRICS_BIND`: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +Depois faça scrape: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +Métricas principais: + +| Métrica | O que monitoriza | +|---------|------------------| +| `dz_feed_up{venue}` | `1` enquanto o multicast dessa venue está ativo, `0` enquanto silencioso. | +| `dz_datagrams_received_total{venue}` | Volume de ingestão por venue. | +| `dz_emit_total{venue,kind}` | Mensagens transmitidas após dedup, por tipo. | +| `dz_quotes_dropped_total{venue}` | Cotações obsoletas/duplicadas suprimidas. | +| `dz_ws_clients` | Clientes WebSocket atualmente conectados. | +| `dz_ws_messages_sent_total{kind}` | Mensagens encaminhadas para clientes. | +| `dz_ws_client_lagged_total` | Vezes que um cliente lento foi descartado para proteger o feed. | + +Uma sonda de liveness `GET /healthz` também é servida no mesmo endereço de bind. + +--- + +## Avançado: Self-hosting + +O contêiner está disponível no GHCR: + +| Ambiente | Imagem | Tag | +|----------|--------|-----| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet (privada) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +Execute manualmente (necessário para opções que o instalador não consegue encaminhar, como `WS_BIND=""`): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**Compilar a partir do código-fonte:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +Um buffer de receção do kernel maior é recomendado para feeds com rajadas: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## Limites e Contrapressão + +| Limite | Padrão | Comportamento quando excedido | +|--------|--------|-------------------------------| +| Clientes simultâneos (`WS_MAX_CLIENTS`) | 64 | Nova conexão é rejeitada. | +| Subscrições por cliente (`WS_MAX_SUBS`) | 256 | `subscribe` é recusado com um erro. | +| Msgs de controlo de entrada / cliente / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Cliente é desconectado. | +| Buffer de broadcast (`WS_BROADCAST_CAPACITY`) | 4096 | Um cliente lento **descarta as mensagens mais antigas** (nunca bloqueia o feed). | + +Como cada `quote` e `depth` é estado completo, um consumidor que perca mensagens sob contrapressão auto-recupera na próxima mensagem — sem necessidade de handshake de ressincronização. + +--- + +## Resolução de Problemas + +### Nenhum shred a chegar à porta local + +- Confirme que o seu acesso está autorizado para os grupos de shreds `edge-solana-*` onchain. +- Verifique se o túnel está ativo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Verifique os logs para erros de join: `sudo docker logs -f doublezero-edge-connect` +- Confirme que `DZ_SHRED_FORWARD` aponta para um destino UDP local acessível. + +### Sem mensagens de uma venue + +- Verifique se o túnel está ativo: `sudo docker exec -it doublezero-edge-connect doublezero status` +- Verifique os logs para erros de join: `sudo docker logs -f doublezero-edge-connect` +- Confirme que o seu acesso está autorizado para essa venue onchain. +- Restrinja a ingestão a essa venue com `DZ_FEEDS=` para isolar o problema. + +### WebSocket conecta mas nenhuma cotação chega + +- As mensagens `instrument` chegam sempre primeiro; as cotações seguem-se assim que o handshake de dados de referência é concluído. Aguarde 10–20 segundos após a conexão antes de concluir que os dados estão em falta. +- Verifique `dz_feed_up{venue}` nas métricas — `0` significa que o multicast está silencioso no seu host. +- Verifique se as regras de firewall permitem UDP multicast na interface `doublezero1`. + +### `dz_ws_client_lagged_total` elevado + +O seu consumidor está a ler mais lentamente do que a ponte está a publicar. Aumente o buffer de broadcast com `WS_BROADCAST_CAPACITY`, reduza o tempo de processamento por mensagem, ou adicione uma thread de leitura dedicada. + +### Contêiner termina imediatamente + +- A ponte requer `--network host` e o dispositivo `/dev/net/tun`; um `docker run` simples sem essas flags falhará. +- Use o comando de uma linha do instalador ou o comando exato `docker run` mostrado em [Self-hosting](#avancado-self-hosting). + +### Túnel GRE não estabelece + +Consulte [Resolução de Problemas](troubleshooting.md) e garanta que o protocolo IP 47 é permitido no seu provedor cloud. Na AWS, desative a verificação source/dest da ENI para o host. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.zh.md b/docs/Edge Market Data Connection.zh.md new file mode 100644 index 0000000..087847a --- /dev/null +++ b/docs/Edge Market Data Connection.zh.md @@ -0,0 +1,465 @@ +--- +description: 运行 doublezero-edge-connect 将 Solana shred 重新转发到本地 UDP 端口,并通过本地 WebSocket 消费标准化的 Edge 市场数据。 +--- + +# Edge 连接 + +!!! warning "连接到 DoubleZero 即表示我同意 [DoubleZero 使用条款](https://doublezero.xyz/terms-protocol)。数据仅供内部使用,不得转播(见第 2(e) 条)。" + +`doublezero-edge-connect` 是一个桥接工具,它接入 **DoubleZero Edge 二进制组播**,并在本地以两种数据源的形式重新提供服务: + +1. **Solana shred 转发** — 去重(可选签名验证)的 shred 分发到一个或多个本地 UDP 目的地,可直接供验证节点或 RPC 使用。 +2. **标准化市场数据** — Edge 交易所数据源经过解码、精度校正,并通过 `ws://host:8081` 上的单一 JSON WebSocket 重新提供服务。 + +两者运行在同一个容器中,使用相同的一行安装命令。根据您的链上授权启用所需的数据源。 + +``` + ┌─ UDP datagrams ──▶ validator / RPC +DZ Edge multicast ──▶ doublezero-edge-connect ─┤ + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine + ws://host:8081 +``` + +--- + +## 系统要求 + +- **Linux/amd64** 主机,具有在链上已授权目标环境的公共 IPv4 地址。 +- **Docker**(一行安装命令会在缺失时自动安装)。 +- **GRE 连接** — 在云服务商处允许 IP 协议 47;在 AWS 上需禁用 ENI 源/目标检查。 +- **DoubleZero 访问密钥**:一个 `DZ_` 前缀的 base64 令牌或密钥对文件路径,通过 [DoubleZero 入驻流程](setup.md)获取。 + +--- + +## 步骤 1:安装并运行 + +一条命令即可准备主机并启动桥接容器。它会加入 DoubleZero 网络,并启动您的授权所涵盖的所有数据源 — shred 转发和/或 `:8081` 上的市场数据 WebSocket: + +=== "Mainnet-beta" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect | bash + ``` + +=== "Testnet" + + ```bash + curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + ``` + +=== "Devnet(私有)" + + ```bash + # 需要具有 read:packages 权限的 GHCR 令牌 + DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash + ``` + +脚本执行内容: + +1. 检查主机是否为 Linux/amd64,确认 Docker 已安装(缺失时提示安装)。 +2. 为 GRE 隧道准备主机内核:加载 `tun`/`ip_gre`,提高 `net.core.rmem_max`,提醒防火墙和云服务商规则。 +3. 加载您的访问密钥(如未设置 `DZ_SECRET`,会提示输入一次)。 +4. 运行桥接容器(`--network host`、`NET_ADMIN`/`NET_RAW`、`/dev/net/tun`)并执行 `doublezero connect multicast`。 + +!!! tip "非交互式安装" + 在管道命令前设置 `DZ_SECRET=DZ_…` 即可完全无人值守运行 — 无需任何确认提示。 + +--- + +## 步骤 2:配置 + +所有配置均通过**管道命令前设置的环境变量**完成,没有配置文件。 + +```bash +DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +### 安装程序变量 + +| 变量 | 默认值 | 用途 | +|------|--------|------| +| `DZ_SECRET` | *(交互式提示)* | `DZ_` 前缀的 base64 令牌**或**密钥对文件路径。令牌注入容器且不写入磁盘;文件以只读方式挂载。 | +| `DZ_ENV` | 取决于脚本 | `mainnet-beta` \| `testnet` \| `devnet`。 | +| `DZ_IMAGE` | 取决于脚本 | 覆盖容器镜像。 | +| `DZ_NAME` | `doublezero-edge-connect` | 容器名称。 | +| `DZ_FEEDS` | *(全部)* | 以逗号分隔的交易所列表,用于缩小市场数据摄入范围(例如 `VenueA,VenueB`)。不影响 Solana shred 转发。 | +| `DZ_ASSUME_YES` | `0` | 跳过确认提示(例如 Docker 安装提示)。 | +| `DZ_GHCR_TOKEN` | — | **仅限 Devnet** — 具有 `read:packages` 权限的 GHCR 令牌(devnet 镜像为私有)。 | +| `DZ_GHCR_USER` | `malbeclabs` | **仅限 Devnet** — 用于登录的 GHCR 用户名。 | + +### 桥接变量 + +安装程序会将**任何非空的**桥接变量直接传递到容器中。常用变量: + +| 变量 | 默认值 | 用途 | +|------|--------|------| +| `DZ_IFACE` | `doublezero1` | 监听的网络接口。 | +| `DZ_RECV_BUF` | — | UDP 接收缓冲区覆盖值(字节)。 | +| `METRICS_BIND` | *(空 / 关闭)* | 启用 Prometheus `/metrics` 端点(例如 `127.0.0.1:9090`)。 | +| `RUST_LOG` | `info` | 日志级别(`debug`、`warn` 等)。 | +| `DZ_SHRED_FORWARD` | — | 转发 shred 的本地 UDP 目的地 — 参见 [Solana Shred 转发](#solana-shred-转发)。 | +| `WS_BIND` | `0.0.0.0:8081` | 市场数据 WebSocket 绑定地址 — 参见[市场数据 WebSocket](#市场数据-websocket)。 | +| `WS_MAX_CLIENTS` | `64` | 最大并发 WebSocket 客户端数。 | +| `WS_INPUT_COINS` | *(空 / 关闭)* | 为列出的交易对启用公共 WebSocket 后备源(例如 `BTC,ETH`)。 | + +**示例:** + +```bash +# 将 shred 转发到本地验证节点/RPC: +DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# 非交互式,testnet: +DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash + +# 缩小市场数据到特定交易所,详细日志,非默认 WS 端口: +DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ + curl -fsSL https://get.doublezero.xyz/connect | bash + +# 启用指标和公共 WS 后备源: +DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ + curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +!!! note + 由于安装程序只转发**非空**值,您无法通过一行安装命令传递空覆盖值(例如 `WS_BIND=""` 以禁用 WebSocket 输出)。此类场景请使用手写的 `docker run` — 参见[自托管](#高级自托管)。 + +--- + +## Solana Shred 转发 + +桥接工具加入 `edge-solana-*` shred 组播组,并将每个数据报分发到一个或多个本地 UDP 目的地 — 直接从 Edge 网络为您的验证节点或 RPC 提供数据。当您的授权中存在这些组播组时,它会在发现时自动激活。 + +```bash +# 默认(仅去重,转发到本地端口 20000): +DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# 带签名验证: +DZ_SHRED_DEDUP_MODE=sigverify \ + DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ + DZ_SHRED_FORWARD=127.0.0.1:20000 \ + DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +| 变量 | 默认值 | 用途 | +|------|--------|------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 转发 shred 的目的地(可重复设置)。 | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(每个 shred 一份副本)、`sigverify`(+ ed25519 验证)、`none`(所有数据报)。 | +| `DZ_SHRED_RPC_URL` | — | Solana RPC 端点;`sigverify` 模式必需。 | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 去重窗口大小。 | + +详见 [Shred 转发](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)了解完整管道和注意事项。 + +--- + +## 市场数据 WebSocket + +打开到 `ws://:8081` 的 WebSocket 连接并读取 JSON 帧。您将接收所有已授权交易所的数据。可通过可选的 `subscribe` 消息将流缩小到特定交易所和交易对。 + +任何支持 WebSocket + JSON 的引擎只需一个轻量级(约 50–100 行)适配器即可消费数据。二进制组播、每个交易所的双端口拆分以及清单/精度握手都保留在桥接内部;消费者唯一需要对接的接口就是 WebSocket JSON。 + +### 连接生命周期 + +每次新连接时,桥接会: + +1. **重放当前合约定义** — 每个已知交易对一条 `instrument` 消息 — 确保消费者在收到首个报价前已获得精度信息。 +2. **重放每个交易对的最新深度快照**(如果逐笔委托数据源处于活跃状态)。 +3. **流式推送** `quote` / `trade` / `midpoint` / `depth` 消息,到达后分发给所有已连接的消费者。 + +``` +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +``` + +### 消息类型 + +每条消息都是一个带有 `type` 字段标签的 JSON 对象: + +| `type` | 含义 | +|--------|------| +| `instrument` | 合约/精度定义。 | +| `quote` | 最优买卖更新(完整状态)。 | +| `trade` | 成交记录(最新成交)。 | +| `midpoint` | 衍生中间价。 | +| `depth` | 完整订单簿深度快照。 | +| `status` | 交易所级别的数据源健康状态变化。 | + +消费者**必须忽略未知的 `type` 值和未知字段**(前向兼容性)。 + +#### `instrument` + +```json +{"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} +``` + +在连接时发送,定义变更时也会发送。`price_exponent` 和 `qty_exponent` 以十的幂次表示交易所的最小价格变动和最小数量步长。 + +#### `quote` + +```json +{ + "type": "quote", + "venue": "ExampleVenue", + "symbol": "SOL", + "bid": 184.20, "ask": 184.21, + "bid_size": 12.5, "ask_size": 8.0, + "bid_n": 3, "ask_n": 2, + "source_ts_ns": 1781019263715344015, + "recv_ts_ns": 1781019263715501230, + "kernel_rx_ts_ns": 1781019263715300010, + "ws_send_ts_ns": 1781019263715600440 +} +``` + +每条 `quote` 都是**完整状态** — 丢失的消息会在下一条报价时自动恢复,无需重新同步。四个时间戳分解了端到端延迟: + +``` +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off +``` + +`0` 是"不可用"的哨兵值 — 将其视为缺失值,而非 1970 年。 + +#### `trade` + +```json +{ + "type": "trade", + "venue": "ExampleVenue", "symbol": "SOL", + "price": 184.20, "size": 3.5, + "aggressor_side": "buy", + "trade_id": 987654, "cumulative_volume": 12500.0, + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`aggressor_side` 为 `"buy"`、`"sell"` 或 `"unknown"`。成交记录是时间点事件,重新连接时不会重放。 + +#### `depth` + +```json +{ + "type": "depth", + "venue": "MboVenue", "symbol": "SOL", + "bids": [[184.20, 12.5], [184.19, 4.0]], + "asks": [[184.21, 8.0], [184.22, 6.5]], + "source_ts_ns": ..., "recv_ts_ns": ..., + "kernel_rx_ts_ns": ..., "ws_send_ts_ns": ... +} +``` + +`bids` 按价格从高到低排序;`asks` 按价格从低到高排序。每条 `depth` 都是**完整快照** — 直接替换,不要合并。 + +#### `status` + +```json +{"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} +``` + +当交易所的报价组播静默时发出(`state:"down"`),恢复时发出(`state:"ok"`)。用于在 UI 中将交易所置灰。报价推送不受状态限制 — 数据源在下一条报价时自动恢复。 + +### 订阅 + +默认情况下您会接收所有数据。发送控制消息以缩小流范围: + +```json +{"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +{"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +省略某个字段将匹配任意值(`{"symbol":"SOL"}` = 所有交易所的 SOL)。`venue` 匹配不区分大小写。 + +**服务器确认:** + +```json +{"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} +``` + +错误返回 `{"channel":"error","error":""}`。 + +### 心跳与活性检测 + +- 服务器每 20 秒发送一次 **WebSocket Ping**;合规客户端自动回复 Pong。 +- 60 秒无活动的客户端将被关闭并清理。 +- 应用层保活:`{"method":"ping"}` → `{"channel":"pong"}`。 + +### 消费者代码骨架 + +```python +import json, websocket + +def on_message(ws, frame): + msg = json.loads(frame) + t = msg.get("type") + if t == "instrument": + register_instrument(msg["venue"], msg["symbol"], + msg["price_exponent"], msg["qty_exponent"]) + elif t == "quote": + on_top_of_book(msg["venue"], msg["symbol"], + msg["bid"], msg["ask"], + msg["bid_size"], msg["ask_size"]) + elif t == "trade": + on_trade(msg["venue"], msg["symbol"], + msg["price"], msg["size"], msg["aggressor_side"]) + elif t == "depth": + replace_book(msg["venue"], msg["symbol"], + msg["bids"], msg["asks"]) + # unknown types: silently ignore (forward compatibility) + +ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) +ws.run_forever() +``` + +### 输入源与 WebSocket 后备机制 + +Edge 组播数据源始终在线。可选的**公共 WebSocket 后备源**可在 Edge 数据源中断时填补缺口: + +```bash +# 为 BTC 和 ETH 启用后备源: +WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +两个数据源在共享仲裁器内按 `(venue, symbol, source_ts)` 逐 tick 竞争。在稳态下 Edge 源获胜(亚毫秒 vs. 互联网上的数十毫秒);当 Edge 出现间隙时,公共副本进行填补。无论某次更新由哪个源送达,WebSocket 输出都是一致的。 + +--- + +## 管理容器 + +```bash +# 查看实时日志 +sudo docker logs -f doublezero-edge-connect + +# 检查隧道状态 +sudo docker exec -it doublezero-edge-connect doublezero status + +# 检查设备延迟 +sudo docker exec -it doublezero-edge-connect doublezero latency + +# 停止并移除 +sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect +``` + +!!! note "无 TLS" + 桥接工具面向可信/本地网络。如果对外暴露 WebSocket 端点,请在反向代理处终止 TLS。 + +--- + +## 监控(Prometheus 指标) + +指标端点**默认关闭**。使用 `METRICS_BIND` 启用: + +```bash +METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash +``` + +然后抓取: + +```bash +curl -s localhost:9090/metrics | grep '^dz_' +``` + +关键指标: + +| 指标 | 追踪内容 | +|------|----------| +| `dz_feed_up{venue}` | 该交易所组播在线时为 `1`,静默时为 `0`。 | +| `dz_datagrams_received_total{venue}` | 每个交易所的摄入量。 | +| `dz_emit_total{venue,kind}` | 去重后按类型广播的消息数。 | +| `dz_quotes_dropped_total{venue}` | 被抑制的过期/重复报价数。 | +| `dz_ws_clients` | 当前已连接的 WebSocket 客户端数。 | +| `dz_ws_messages_sent_total{kind}` | 转发给客户端的消息数。 | +| `dz_ws_client_lagged_total` | 为保护数据源而淘汰慢速客户端的次数。 | + +同一绑定地址上还提供 `GET /healthz` 存活探针。 + +--- + +## 高级:自托管 + +容器可在 GHCR 上获取: + +| 环境 | 镜像 | 标签 | +|------|------|------| +| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | +| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | +| Devnet(私有) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | + +手动运行(适用于安装程序无法转发的选项,如 `WS_BIND=""`): + +```bash +docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ + -e DZ_SECRET=DZ_… \ + -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ + -e WS_BIND=0.0.0.0:8081 \ + -e METRICS_BIND=127.0.0.1:9090 \ + ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta +``` + +**从源码构建:** + +```bash +git clone https://github.com/malbeclabs/doublezero-edge-connect +cd doublezero-edge-connect +cargo build --release +cargo test + +./target/release/doublezero-edge-connect \ + --iface doublezero1 \ + --ws-bind 0.0.0.0:8081 +``` + +建议为突发数据源设置更大的内核接收缓冲区: + +```bash +sudo sysctl -w net.core.rmem_max=268435456 +``` + +--- + +## 限制与背压 + +| 限制 | 默认值 | 超出时的行为 | +|------|--------|------------| +| 并发客户端数 (`WS_MAX_CLIENTS`) | 64 | 新连接被拒绝。 | +| 每个客户端的订阅数 (`WS_MAX_SUBS`) | 256 | `subscribe` 被拒绝并返回错误。 | +| 每客户端每分钟入站控制消息数 (`WS_MAX_INBOUND_PER_MIN`) | 600 | 客户端被断开连接。 | +| 广播缓冲区 (`WS_BROADCAST_CAPACITY`) | 4096 | 慢速客户端**丢弃最旧的消息**(永远不会阻塞数据源)。 | + +由于每条 `quote` 和 `depth` 都是完整状态,消费者在背压下丢失消息后会在下一条消息时自动恢复 — 无需重新同步握手。 + +--- + +## 故障排除 + +### 本地端口未收到 shred + +- 确认您的访问已在链上获得 `edge-solana-*` shred 组的授权。 +- 验证隧道是否正常:`sudo docker exec -it doublezero-edge-connect doublezero status` +- 检查日志中的加入错误:`sudo docker logs -f doublezero-edge-connect` +- 确认 `DZ_SHRED_FORWARD` 指向一个可达的本地 UDP 目的地。 + +### 未收到某个交易所的消息 + +- 验证隧道是否正常:`sudo docker exec -it doublezero-edge-connect doublezero status` +- 检查日志中的加入错误:`sudo docker logs -f doublezero-edge-connect` +- 确认您的访问已在链上获得该交易所的授权。 +- 使用 `DZ_FEEDS=` 缩小摄入范围以隔离问题。 + +### WebSocket 已连接但未收到报价 + +- `instrument` 消息始终先到达;报价在参考数据握手完成后才会跟进。连接后请等待 10–20 秒再判断数据是否缺失。 +- 检查指标中的 `dz_feed_up{venue}` — `0` 表示组播在您的主机上处于静默状态。 +- 验证防火墙规则是否允许 `doublezero1` 接口上的组播 UDP。 + +### `dz_ws_client_lagged_total` 值过高 + +您的消费者读取速度慢于桥接发布速度。请使用 `WS_BROADCAST_CAPACITY` 增大广播缓冲区,减少每条消息的处理时间,或添加专用读取线程。 + +### 容器立即退出 + +- 桥接工具需要 `--network host` 和 `/dev/net/tun` 设备;没有这些标志的普通 `docker run` 会失败。 +- 请使用一行安装命令或[自托管](#高级自托管)中展示的完整 `docker run` 命令。 + +### GRE 隧道无法建立 + +请参阅[故障排除](troubleshooting.md),并确保在云服务商处已允许 IP 协议 47。在 AWS 上,需为主机禁用 ENI 源/目标检查。 \ No newline at end of file From a07d77e459464899a3c9191c8f0c2b730fbf6ce9 Mon Sep 17 00:00:00 2001 From: Juan Olveira Date: Thu, 2 Jul 2026 14:15:46 +0000 Subject: [PATCH 3/4] docs: update Edge Connection guide for upstream changes Sync docs/Edge Market Data Connection.md with doublezero-edge-connect changes since the guide was written: - Add DZ_SHRED_DISABLE shred-forwarder kill switch (#54) - Document Phoenix trade backstop + WS_INPUT_URL/PHOENIX_WS_INPUT_* (#56) - Add onchain access-pass pre-check + DZ_CLIENT_IP/DZ_LEDGER_RPC_URL (#48) - Fix WS_BIND="" note: now forwarded through the one-liner (#61) - Correct RUST_LOG and DZ_RECV_BUF defaults - Note subscription-gated WS activation (#62) - Add dz_quotes_admitted_total{venue,publisher} metric (#60) Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/Edge Market Data Connection.md | 38 +++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/Edge Market Data Connection.md b/docs/Edge Market Data Connection.md index 1d40aa7..fe77d49 100644 --- a/docs/Edge Market Data Connection.md +++ b/docs/Edge Market Data Connection.md @@ -56,9 +56,9 @@ One command prepares the host and starts the bridge container. It joins the Doub What the script does: -1. Checks that the host is Linux/amd64, ensures Docker is present (offers to install it). -2. Prepares the host kernel for the GRE tunnel: loads `tun`/`ip_gre`, raises `net.core.rmem_max`, warns about firewall and cloud-provider rules. -3. Loads your access secret (prompted once if `DZ_SECRET` is not set). +1. Checks that the host is Linux/amd64. +2. Loads your access secret (prompted once if `DZ_SECRET` is not set) and **verifies its access pass onchain before installing anything** — a pure host-side check against the ledger's public JSON-RPC. If the pass is bound to a different IP than the host's, it aborts (when the IP was given explicitly via `DZ_CLIENT_IP`) or warns and continues (when the IP was only auto-detected, which can be wrong behind NAT), leaving `doublezero connect` as the real check. +3. Ensures Docker is present (offers to install it) and prepares the host kernel for the GRE tunnel: loads `tun`/`ip_gre`, raises `net.core.rmem_max`, warns about firewall and cloud-provider rules. 4. Runs the bridge container (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) and executes `doublezero connect multicast`. !!! tip "Non-interactive install" @@ -83,6 +83,8 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash | `DZ_IMAGE` | per script | Override the container image. | | `DZ_NAME` | `doublezero-edge-connect` | Container name. | | `DZ_FEEDS` | *(all)* | Comma-separated venues to narrow market-data ingestion (e.g. `VenueA,VenueB`). Does not affect Solana shred forwarding. | +| `DZ_CLIENT_IP` | *(auto-detected)* | Override the public IPv4 used by the onchain access-pass pre-check. Set it when auto-detection is wrong (e.g. behind NAT) so the pre-check can confirm rather than only warn. | +| `DZ_LEDGER_RPC_URL` | per env | Override the DoubleZero ledger RPC endpoint used by the pre-check. | | `DZ_ASSUME_YES` | `0` | Skip confirmation prompts (e.g. the Docker install prompt). | | `DZ_GHCR_TOKEN` | — | **Devnet only** — a GHCR token with `read:packages` (devnet image is private). | | `DZ_GHCR_USER` | `malbeclabs` | **Devnet only** — GHCR username for the login. | @@ -94,13 +96,16 @@ The installer forwards **any non-empty** bridge variable straight through to the | Variable | Default | Purpose | |----------|---------|---------| | `DZ_IFACE` | `doublezero1` | Network interface to listen on. | -| `DZ_RECV_BUF` | — | UDP receive buffer override (bytes). | +| `DZ_RECV_BUF` | `8388608` | UDP receive buffer override (bytes; default 8 MiB). | | `METRICS_BIND` | *(empty / off)* | Enable the Prometheus `/metrics` endpoint (e.g. `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | Log level (`debug`, `warn`, etc.). | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | Log level (`debug`, `warn`, etc.). | | `DZ_SHRED_FORWARD` | — | Local UDP destination(s) for forwarded shreds — see [Solana Shred Forwarding](#solana-shred-forwarding). | | `WS_BIND` | `0.0.0.0:8081` | Market-data WebSocket bind address — see [Market Data WebSocket](#market-data-websocket). | | `WS_MAX_CLIENTS` | `64` | Maximum concurrent WebSocket clients. | -| `WS_INPUT_COINS` | *(empty / off)* | Enable the public WebSocket backstop for listed symbols (e.g. `BTC,ETH`). | +| `WS_INPUT_COINS` | *(empty / off)* | Enable the Hyperliquid public WebSocket backstop for listed symbols (e.g. `BTC,ETH`) — see [Input sources](#input-sources-and-the-websocket-backstop). | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | Hyperliquid public WebSocket URL for the backstop. | +| `PHOENIX_WS_INPUT_MARKETS` | *(empty / off)* | Enable the Phoenix public WebSocket backstop (trades only) for listed tickers (e.g. `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | Phoenix public WebSocket URL for the backstop. | **Examples:** @@ -122,7 +127,7 @@ DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ ``` !!! note - Because the installer only forwards **non-empty** values, you cannot pass an empty override (e.g. `WS_BIND=""` to disable the WebSocket sink) through the one-liner. Use a hand-written `docker run` for that — see [Self-hosting](#advanced-self-hosting). + The installer forwards only **non-empty** values, with one exception: `WS_BIND` is forwarded even when set empty, so `WS_BIND=""` **does** disable the WebSocket sink through the one-liner. For any other variable, an empty override cannot be passed through the pipe — use a hand-written `docker run` for that (see [Self-hosting](#advanced-self-hosting)). --- @@ -144,6 +149,7 @@ DZ_SHRED_DEDUP_MODE=sigverify \ | Variable | Default | Purpose | |----------|---------|---------| | `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destination(s) for forwarded shreds (repeatable). | +| `DZ_SHRED_DISABLE` | `0` | Master opt-out (`--shred-forward-disable`). Keeps the forwarder off regardless of what your authorization grants — set it when no local consumer is listening, to avoid burning CPU forwarding the shred firehose to nowhere. | | `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (one copy per shred), `sigverify` (+ ed25519 verification), `none` (all datagrams). | | `DZ_SHRED_RPC_URL` | — | Solana RPC endpoint; required by `sigverify` mode. | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Size of the dedup window. | @@ -158,6 +164,9 @@ Open a WebSocket to `ws://:8081` and read JSON frames. You receive all ven Any engine that speaks WebSocket + JSON can consume it with a thin (~50–100 line) adapter. The binary multicast, the two-port per-venue split, and the manifest/precision handshake all stay inside the bridge; the only contract a consumer codes against is the WebSocket JSON. +!!! note + The WebSocket sink comes up only when at least one market-data feed is active for your authorization — a shreds-only host serves no WebSocket. Activation is driven by an onchain subscription reconciler that refreshes every 30s (`--subscription-refresh-secs`); `--subscription-gating-disable` opts out of the gating. + ### Connection lifecycle On each new connection the bridge: @@ -312,14 +321,22 @@ ws.run_forever() ### Input sources and the WebSocket backstop -The Edge multicast feed is always-on. An optional **public WebSocket backstop** can fill gaps when the Edge feed stalls: +The Edge multicast feed is always-on. Optional **public WebSocket backstops** can fill gaps when the Edge feed stalls. Two are available, each off by default and enabled independently per venue: + +| Backstop | Enable with | Covers | Default URL | +|----------|-------------|--------|-------------| +| **Hyperliquid** | `WS_INPUT_COINS` (e.g. `BTC,ETH`) | quotes + trades | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (tickers, e.g. `SOL,BTC`) | **trades only** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# Enable the backstop for BTC and ETH: +# Enable the Hyperliquid backstop for BTC and ETH: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Enable the Phoenix trade backstop for SOL: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -The two sources race per `(venue, symbol, source_ts)` tick inside a shared arbiter. In steady state the Edge source wins (sub-ms vs. tens of ms over the internet); when the Edge gaps, the public copy fills in. The WebSocket output is identical regardless of which source delivered a given update. +For each `(venue, symbol, source_ts)` tick, the Edge and public sources race inside a shared arbiter. In steady state the Edge source wins (sub-ms vs. tens of ms over the internet); when the Edge gaps, the public copy fills in. The WebSocket output is identical regardless of which source delivered a given update. (Phoenix backstops trades only — Edge remains the sole source of Phoenix quotes.) --- @@ -365,6 +382,7 @@ Key metrics: | `dz_feed_up{venue}` | `1` while that venue's multicast is live, `0` while silent. | | `dz_datagrams_received_total{venue}` | Ingest volume per venue. | | `dz_emit_total{venue,kind}` | Messages broadcast after dedup, by type. | +| `dz_quotes_admitted_total{venue,publisher}` | Quotes admitted by the arbiter, attributed to the winning source. A rise in `publisher="public"` means a backstop is filling an Edge gap (vs. `publisher="edge"` in steady state). | | `dz_quotes_dropped_total{venue}` | Stale/duplicate quotes suppressed. | | `dz_ws_clients` | Currently connected WebSocket clients. | | `dz_ws_messages_sent_total{kind}` | Messages forwarded to clients. | From 66608eaff876e8d0751c1945631e915a73d4f6ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:34:01 +0000 Subject: [PATCH 4/4] chore: auto-translate docs --- docs/Edge Market Data Connection.es.md | 170 +++++++++------- docs/Edge Market Data Connection.fr.md | 195 +++++++++--------- docs/Edge Market Data Connection.it.md | 186 +++++++++-------- docs/Edge Market Data Connection.ja.md | 269 ++++++++++--------------- docs/Edge Market Data Connection.ko.md | 240 +++++++++------------- docs/Edge Market Data Connection.pt.md | 148 ++++++++------ docs/Edge Market Data Connection.zh.md | 220 ++++++++++---------- 7 files changed, 687 insertions(+), 741 deletions(-) diff --git a/docs/Edge Market Data Connection.es.md b/docs/Edge Market Data Connection.es.md index dbde7df..2975c30 100644 --- a/docs/Edge Market Data Connection.es.md +++ b/docs/Edge Market Data Connection.es.md @@ -4,14 +4,14 @@ description: Ejecute doublezero-edge-connect para reenviar shreds de Solana a un # Conexión Edge -!!! warning "Al conectarme a DoubleZero acepto los [Términos de Uso de DoubleZero](https://doublezero.xyz/terms-protocol). Los datos son únicamente para sus propósitos internos y no pueden ser retransmitidos (ver Sección 2(e))." +!!! warning "Al conectarme a DoubleZero acepto los [Términos de Uso de DoubleZero](https://doublezero.xyz/terms-protocol). Los datos son únicamente para uso interno y no pueden ser retransmitidos (véase la Sección 2(e))." -`doublezero-edge-connect` es un puente que se une al **multicast binario de DoubleZero Edge** y lo re-sirve localmente como dos feeds: +`doublezero-edge-connect` es un puente que se une al **multicast binario de DoubleZero Edge** y lo redistribuye localmente como dos flujos: 1. **Reenvío de shreds de Solana** — shreds deduplicados (opcionalmente con verificación de firma) distribuidos a uno o más destinos UDP locales, listos para su validador o RPC. -2. **Datos de mercado normalizados** — feeds de venues de Edge decodificados, con precisión corregida, y re-servidos como un único WebSocket JSON en `ws://host:8081`. +2. **Datos de mercado normalizados** — feeds de venues de Edge decodificados, con precisión corregida, y redistribuidos como un único WebSocket JSON en `ws://host:8081`. -Ambos se ejecutan desde el mismo contenedor y la misma instalación de una sola línea. Habilite los feeds que su autorización onchain le otorgue. +Ambos se ejecutan desde el mismo contenedor y la misma instalación de una sola línea. Habilite los feeds que su autorización onchain le permita. ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -26,14 +26,14 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ - Host **Linux/amd64** con una dirección IPv4 pública autorizada onchain para el entorno objetivo. - **Docker** (el instalador de una línea lo instala si no está presente). -- **Conectividad GRE** — permita el protocolo IP 47 en su proveedor de nube; en AWS deshabilite la verificación de origen/destino del ENI. +- **Conectividad GRE** — permita el protocolo IP 47 en su proveedor de nube; en AWS deshabilite la verificación source/dest del ENI. - Un **secreto de acceso DoubleZero**: un token base64 con prefijo `DZ_` o una ruta a un archivo de keypair, obtenido del proceso de [incorporación a DoubleZero](setup.md). --- ## Paso 1: Instalar y Ejecutar -Un solo comando prepara el host e inicia el contenedor puente. Se une a la red DoubleZero e inicia cada feed que su autorización otorgue — reenvío de shreds y/o el WebSocket de datos de mercado en `:8081`: +Un solo comando prepara el host e inicia el contenedor puente. Se une a la red DoubleZero e inicia cada feed que su autorización permita — reenvío de shreds y/o el WebSocket de datos de mercado en `:8081`: === "Mainnet-beta" @@ -56,9 +56,9 @@ Un solo comando prepara el host e inicia el contenedor puente. Se une a la red D Lo que hace el script: -1. Verifica que el host sea Linux/amd64, se asegura de que Docker esté presente (ofrece instalarlo). -2. Prepara el kernel del host para el túnel GRE: carga `tun`/`ip_gre`, aumenta `net.core.rmem_max`, advierte sobre reglas de firewall y del proveedor de nube. -3. Carga su secreto de acceso (se solicita una vez si `DZ_SECRET` no está configurado). +1. Verifica que el host sea Linux/amd64. +2. Carga su secreto de acceso (se solicita una vez si `DZ_SECRET` no está configurado) y **verifica su pase de acceso onchain antes de instalar nada** — una comprobación puramente del lado del host contra el JSON-RPC público del ledger. Si el pase está vinculado a una IP diferente a la del host, aborta (cuando la IP se proporcionó explícitamente vía `DZ_CLIENT_IP`) o advierte y continúa (cuando la IP fue solo autodetectada, lo cual puede ser incorrecto detrás de NAT), dejando `doublezero connect` como la verificación real. +3. Se asegura de que Docker esté presente (ofrece instalarlo) y prepara el kernel del host para el túnel GRE: carga `tun`/`ip_gre`, eleva `net.core.rmem_max`, advierte sobre reglas de firewall y del proveedor de nube. 4. Ejecuta el contenedor puente (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) y ejecuta `doublezero connect multicast`. !!! tip "Instalación no interactiva" @@ -76,31 +76,36 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash ### Variables del instalador -| Variable | Valor predeterminado | Propósito | -|----------|---------|---------| -| `DZ_SECRET` | *(solicitado)* | Token base64 con prefijo `DZ_` **o** ruta a un archivo de keypair. Un token se inyecta en el contenedor y nunca se escribe en disco; un archivo se monta en modo solo lectura. | -| `DZ_ENV` | por script | `mainnet-beta` \| `testnet` \| `devnet`. | -| `DZ_IMAGE` | por script | Sobrescribir la imagen del contenedor. | +| Variable | Valor por defecto | Propósito | +|----------|-------------------|-----------| +| `DZ_SECRET` | *(se solicita)* | Token base64 con prefijo `DZ_` **o** ruta a un archivo de keypair. Un token se inyecta en el contenedor y nunca se escribe en disco; un archivo se monta como solo lectura (bind-mount). | +| `DZ_ENV` | según el script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | según el script | Sobrescribir la imagen del contenedor. | | `DZ_NAME` | `doublezero-edge-connect` | Nombre del contenedor. | -| `DZ_FEEDS` | *(todos)* | Venues separados por comas para acotar la ingesta de datos de mercado (ej. `VenueA,VenueB`). No afecta el reenvío de shreds de Solana. | +| `DZ_FEEDS` | *(todos)* | Venues separados por comas para limitar la ingesta de datos de mercado (ej. `VenueA,VenueB`). No afecta al reenvío de shreds de Solana. | +| `DZ_CLIENT_IP` | *(autodetectada)* | Sobrescribir la IPv4 pública usada por la pre-verificación del pase de acceso onchain. Configúrela cuando la autodetección sea incorrecta (ej. detrás de NAT) para que la pre-verificación pueda confirmar en lugar de solo advertir. | +| `DZ_LEDGER_RPC_URL` | según el entorno | Sobrescribir el endpoint RPC del ledger de DoubleZero usado por la pre-verificación. | | `DZ_ASSUME_YES` | `0` | Omitir prompts de confirmación (ej. el prompt de instalación de Docker). | -| `DZ_GHCR_TOKEN` | — | **Solo devnet** — un token GHCR con `read:packages` (la imagen de devnet es privada). | -| `DZ_GHCR_USER` | `malbeclabs` | **Solo devnet** — nombre de usuario GHCR para el login. | +| `DZ_GHCR_TOKEN` | — | **Solo Devnet** — un token GHCR con `read:packages` (la imagen de devnet es privada). | +| `DZ_GHCR_USER` | `malbeclabs` | **Solo Devnet** — nombre de usuario GHCR para el login. | ### Variables del puente El instalador reenvía **cualquier variable no vacía** del puente directamente al contenedor. Las más comunes: -| Variable | Valor predeterminado | Propósito | -|----------|---------|---------| +| Variable | Valor por defecto | Propósito | +|----------|-------------------|-----------| | `DZ_IFACE` | `doublezero1` | Interfaz de red en la que escuchar. | -| `DZ_RECV_BUF` | — | Sobrescritura del buffer de recepción UDP (bytes). | +| `DZ_RECV_BUF` | `8388608` | Sobrescritura del buffer de recepción UDP (bytes; por defecto 8 MiB). | | `METRICS_BIND` | *(vacío / desactivado)* | Habilitar el endpoint Prometheus `/metrics` (ej. `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | Nivel de log (`debug`, `warn`, etc.). | -| `DZ_SHRED_FORWARD` | — | Destino(s) UDP locales para shreds reenviados — ver [Reenvío de Shreds de Solana](#reenvio-de-shreds-de-solana). | -| `WS_BIND` | `0.0.0.0:8081` | Dirección de enlace del WebSocket de datos de mercado — ver [WebSocket de Datos de Mercado](#websocket-de-datos-de-mercado). | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | Nivel de log (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destino(s) UDP local(es) para shreds reenviados — véase [Reenvío de Shreds de Solana](#reenvio-de-shreds-de-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Dirección de enlace del WebSocket de datos de mercado — véase [WebSocket de Datos de Mercado](#websocket-de-datos-de-mercado). | | `WS_MAX_CLIENTS` | `64` | Máximo de clientes WebSocket concurrentes. | -| `WS_INPUT_COINS` | *(vacío / desactivado)* | Habilitar el respaldo público de WebSocket para los símbolos listados (ej. `BTC,ETH`). | +| `WS_INPUT_COINS` | *(vacío / desactivado)* | Habilitar el respaldo de WebSocket público de Hyperliquid para símbolos listados (ej. `BTC,ETH`) — véase [Fuentes de entrada](#fuentes-de-entrada-y-el-respaldo-websocket). | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | URL del WebSocket público de Hyperliquid para el respaldo. | +| `PHOENIX_WS_INPUT_MARKETS` | *(vacío / desactivado)* | Habilitar el respaldo de WebSocket público de Phoenix (solo trades) para tickers listados (ej. `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | URL del WebSocket público de Phoenix para el respaldo. | **Ejemplos:** @@ -112,23 +117,23 @@ DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ # No interactivo, testnet: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# Acotar datos de mercado a venues específicos, logging verbose, puerto WS no predeterminado: +# Limitar datos de mercado a venues específicos, logging detallado, puerto WS no predeterminado: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# Habilitar métricas y un respaldo público de WS: +# Habilitar métricas y un respaldo WS público: DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ curl -fsSL https://get.doublezero.xyz/connect | bash ``` !!! note - Dado que el instalador solo reenvía valores **no vacíos**, no puede pasar una sobrescritura vacía (ej. `WS_BIND=""` para deshabilitar el sink WebSocket) a través del one-liner. Use un `docker run` escrito manualmente para eso — ver [Autoalojamiento](#avanzado-autoalojamiento). + El instalador reenvía solo valores **no vacíos**, con una excepción: `WS_BIND` se reenvía incluso cuando se establece vacío, por lo que `WS_BIND=""` **sí** desactiva el sink WebSocket a través del instalador de una línea. Para cualquier otra variable, una sobrescritura vacía no puede pasarse a través del pipe — use un `docker run` manual para eso (véase [Autoalojamiento](#avanzado-autoalojamiento)). --- ## Reenvío de Shreds de Solana -El puente se une a los grupos multicast `edge-solana-*` de shreds y distribuye cada datagrama a uno o más destinos UDP locales — alimentando su validador o RPC directamente desde la red Edge. Se activa automáticamente al descubrir cuando esos grupos están presentes en su autorización. +El puente se une a los grupos multicast de shreds `edge-solana-*` y distribuye cada datagrama a uno o más destinos UDP locales — alimentando su validador o RPC directamente desde la red Edge. Se activa automáticamente al descubrimiento cuando esos grupos están presentes en su autorización. ```bash # Predeterminado (solo dedup, reenviar al puerto local 20000): @@ -141,41 +146,45 @@ DZ_SHRED_DEDUP_MODE=sigverify \ DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -| Variable | Valor predeterminado | Propósito | -|----------|---------|---------| +| Variable | Valor por defecto | Propósito | +|----------|-------------------|-----------| | `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destino(s) para shreds reenviados (repetible). | +| `DZ_SHRED_DISABLE` | `0` | Desactivación maestra (`--shred-forward-disable`). Mantiene el reenviador apagado independientemente de lo que su autorización permita — configúrelo cuando ningún consumidor local esté escuchando, para evitar gastar CPU reenviando el flujo completo de shreds a la nada. | | `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (una copia por shred), `sigverify` (+ verificación ed25519), `none` (todos los datagramas). | | `DZ_SHRED_RPC_URL` | — | Endpoint RPC de Solana; requerido por el modo `sigverify`. | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Tamaño de la ventana de deduplicación. | -Ver [Reenvío de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para el pipeline completo y advertencias. +Véase [Reenvío de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para el pipeline completo y las consideraciones. --- ## WebSocket de Datos de Mercado -Abra un WebSocket a `ws://:8081` y lea tramas JSON. Recibirá todos los venues para los que esté autorizado. Un mensaje opcional `subscribe` acota el flujo a venues y símbolos específicos. +Abra un WebSocket a `ws://:8081` y lea frames JSON. Recibirá todos los venues para los que esté autorizado. Un mensaje `subscribe` opcional reduce el flujo a venues y símbolos específicos. -Cualquier motor que hable WebSocket + JSON puede consumirlo con un adaptador ligero (~50–100 líneas). El multicast binario, la división de dos puertos por venue y el handshake de manifiesto/precisión permanecen dentro del puente; el único contrato contra el que un consumidor programa es el JSON del WebSocket. +Cualquier motor que hable WebSocket + JSON puede consumirlo con un adaptador pequeño (~50–100 líneas). El multicast binario, la división de dos puertos por venue y el handshake de manifiesto/precisión permanecen dentro del puente; el único contrato contra el que un consumidor programa es el WebSocket JSON. + +!!! note + El sink WebSocket se activa solo cuando al menos un feed de datos de mercado está activo para su autorización — un host solo de shreds no sirve WebSocket. La activación es controlada por un reconciliador de suscripciones onchain que se actualiza cada 30s (`--subscription-refresh-secs`); `--subscription-gating-disable` desactiva el control de acceso. ### Ciclo de vida de la conexión En cada nueva conexión el puente: -1. **Reproduce las definiciones de instrumentos actuales** — un mensaje `instrument` por cada símbolo conocido — para que el consumidor tenga la precisión antes de la primera cotización. +1. **Reproduce las definiciones de instrumentos actuales** — un mensaje `instrument` por símbolo conocido — para que el consumidor tenga la precisión antes de la primera cotización. 2. **Reproduce la última instantánea de profundidad** por símbolo (si el feed Market-by-Order está activo). -3. **Transmite** mensajes `quote` / `trade` / `midpoint` / `depth` a medida que llegan, distribuidos a todos los consumidores conectados. +3. **Transmite** mensajes `quote` / `trade` / `midpoint` / `depth` conforme llegan, distribuidos a todos los consumidores conectados. ``` -connect → instrument (×N) → depth (×M, últimos libros) → quote → trade → depth → … +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … ``` ### Tipos de mensaje -Cada mensaje es un objeto JSON etiquetado con un campo `type`: +Cada mensaje es un objeto JSON etiquetado por un campo `type`: | `type` | Significado | -|--------|---------| +|--------|-------------| | `instrument` | Definición de instrumento/precisión. | | `quote` | Actualización del tope del libro (estado completo). | | `trade` | Impresión de operación (última venta). | @@ -183,7 +192,7 @@ Cada mensaje es un objeto JSON etiquetado con un campo `type`: | `depth` | Instantánea completa de profundidad del libro de órdenes. | | `status` | Transición de salud del feed a nivel de venue. | -Los consumidores **deben ignorar valores `type` desconocidos y campos desconocidos** (compatibilidad hacia adelante). +Los consumidores **deben ignorar valores `type` desconocidos y campos desconocidos** (compatibilidad futura). #### `instrument` @@ -191,7 +200,7 @@ Los consumidores **deben ignorar valores `type` desconocidos y campos desconocid {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -Se envía al conectar y cada vez que las definiciones cambian. `price_exponent` y `qty_exponent` indican el tick size y el paso de tamaño del venue como potencias de diez. +Se envía al conectar y cada vez que las definiciones cambian. `price_exponent` y `qty_exponent` dan el tick size y el paso de tamaño del venue como potencias de diez. #### `quote` @@ -210,11 +219,11 @@ Se envía al conectar y cada vez que las definiciones cambian. `price_exponent` } ``` -Cada `quote` es **estado completo** — un mensaje perdido se auto-recupera con la siguiente cotización, sin necesidad de resincronización. Las cuatro marcas de tiempo descomponen la latencia de extremo a extremo: +Cada `quote` es **estado completo** — un mensaje perdido se autocorrige con la siguiente cotización, no se necesita resincronización. Las cuatro marcas de tiempo descomponen la latencia de extremo a extremo: ``` -source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (recepción del consumidor) - libro del venue llegada por cable post-decodificación entrega al WS +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off ``` `0` es el valor centinela para "no disponible" — trátelo como ausente, no como 1970. @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (recepció } ``` -`aggressor_side` es `"buy"`, `"sell"` o `"unknown"`. Las operaciones son eventos puntuales y no se reproducen al reconectar. +`aggressor_side` es `"buy"`, `"sell"` o `"unknown"`. Los trades son eventos puntuales y no se reproducen al reconectar. #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (recepció } ``` -Los `bids` están ordenados del precio más alto primero; los `asks` están ordenados del precio más bajo primero. Cada `depth` es una **instantánea completa** — reemplace, no combine. +Los `bids` están ordenados del precio más alto al más bajo; los `asks` están ordenados del precio más bajo al más alto. Cada `depth` es una **instantánea completa** — reemplace, no fusione. #### `status` @@ -256,11 +265,11 @@ Los `bids` están ordenados del precio más alto primero; los `asks` están orde {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -Se emite en el edge cuando el multicast de cotizaciones de un venue queda en silencio (`state:"down"`) o se recupera (`state:"ok"`). Úselo para atenuar un venue en su interfaz. La entrega de cotizaciones no depende del estado — el feed se auto-recupera con la siguiente cotización. +Se emite en el edge cuando el multicast de cotizaciones de un venue queda en silencio (`state:"down"`) o se recupera (`state:"ok"`). Úselo para atenuar un venue en su interfaz. La entrega de cotizaciones no está condicionada al estado — el feed se autocorrige con la siguiente cotización. ### Suscripciones -Por defecto recibe todo. Envíe un mensaje de control para acotar el flujo: +Por defecto recibe todo. Envíe un mensaje de control para reducir el flujo: ```json {"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} @@ -275,11 +284,11 @@ Omitir un campo coincide con cualquier valor (`{"symbol":"SOL"}` = SOL en todos {"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -Los errores devuelven `{"channel":"error","error":""}`. +Los errores devuelven `{"channel":"error","error":""}`. -### Heartbeat y comprobación de vida +### Heartbeat y verificación de actividad -- El servidor envía un **Ping de WebSocket** cada 20 segundos; los clientes compatibles responden automáticamente con Pong. +- El servidor envía un **WebSocket Ping** cada 20 segundos; los clientes compatibles responden Pong automáticamente. - Los clientes silenciosos durante 60 segundos son cerrados y eliminados. - Keepalive a nivel de aplicación: `{"method":"ping"}` → `{"channel":"pong"}`. @@ -304,7 +313,7 @@ def on_message(ws, frame): elif t == "depth": replace_book(msg["venue"], msg["symbol"], msg["bids"], msg["asks"]) - # tipos desconocidos: ignorar silenciosamente (compatibilidad hacia adelante) + # tipos desconocidos: ignorar silenciosamente (compatibilidad futura) ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() @@ -312,14 +321,22 @@ ws.run_forever() ### Fuentes de entrada y el respaldo WebSocket -El feed multicast de Edge está siempre activo. Un **respaldo público por WebSocket** opcional puede llenar vacíos cuando el feed Edge se detiene: +El feed multicast Edge está siempre activo. **Respaldos de WebSocket públicos** opcionales pueden llenar vacíos cuando el feed Edge se detiene. Hay dos disponibles, cada uno desactivado por defecto y habilitado independientemente por venue: + +| Respaldo | Habilitar con | Cubre | URL por defecto | +|----------|---------------|-------|-----------------| +| **Hyperliquid** | `WS_INPUT_COINS` (ej. `BTC,ETH`) | cotizaciones + trades | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (tickers, ej. `SOL,BTC`) | **solo trades** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# Habilitar el respaldo para BTC y ETH: +# Habilitar el respaldo de Hyperliquid para BTC y ETH: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Habilitar el respaldo de trades de Phoenix para SOL: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -Las dos fuentes compiten por cada tick `(venue, symbol, source_ts)` dentro de un árbitro compartido. En estado estable la fuente Edge gana (sub-ms vs. decenas de ms por internet); cuando Edge tiene vacíos, la copia pública los completa. La salida WebSocket es idéntica independientemente de qué fuente entregó una actualización determinada. +Para cada tick `(venue, symbol, source_ts)`, las fuentes Edge y pública compiten dentro de un árbitro compartido. En estado estable, la fuente Edge gana (sub-ms vs. decenas de ms por internet); cuando Edge tiene vacíos, la copia pública los llena. La salida WebSocket es idéntica independientemente de qué fuente entregó una actualización dada. (Los respaldos de Phoenix cubren solo trades — Edge sigue siendo la única fuente de cotizaciones de Phoenix.) --- @@ -344,7 +361,7 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne --- -## Monitoreo (Métricas de Prometheus) +## Monitoreo (Métricas Prometheus) El endpoint de métricas está **desactivado por defecto**. Habilítelo con `METRICS_BIND`: @@ -352,7 +369,7 @@ El endpoint de métricas está **desactivado por defecto**. Habilítelo con `MET METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -Luego haga scraping: +Luego consulte: ```bash curl -s localhost:9090/metrics | grep '^dz_' @@ -361,16 +378,17 @@ curl -s localhost:9090/metrics | grep '^dz_' Métricas clave: | Métrica | Qué rastrea | -|--------|---------------| +|---------|-------------| | `dz_feed_up{venue}` | `1` mientras el multicast de ese venue está activo, `0` mientras está en silencio. | | `dz_datagrams_received_total{venue}` | Volumen de ingesta por venue. | -| `dz_emit_total{venue,kind}` | Mensajes difundidos después de la deduplicación, por tipo. | +| `dz_emit_total{venue,kind}` | Mensajes emitidos después de dedup, por tipo. | +| `dz_quotes_admitted_total{venue,publisher}` | Cotizaciones admitidas por el árbitro, atribuidas a la fuente ganadora. Un aumento en `publisher="public"` significa que un respaldo está llenando un vacío de Edge (vs. `publisher="edge"` en estado estable). | | `dz_quotes_dropped_total{venue}` | Cotizaciones obsoletas/duplicadas suprimidas. | -| `dz_ws_clients` | Clientes WebSocket conectados actualmente. | +| `dz_ws_clients` | Clientes WebSocket actualmente conectados. | | `dz_ws_messages_sent_total{kind}` | Mensajes reenviados a los clientes. | | `dz_ws_client_lagged_total` | Veces que un cliente lento fue descartado para proteger el feed. | -Una sonda de vida `GET /healthz` también se sirve en la misma dirección de enlace. +Un probe de liveness `GET /healthz` también se sirve en la misma dirección de enlace. --- @@ -379,7 +397,7 @@ Una sonda de vida `GET /healthz` también se sirve en la misma dirección de enl El contenedor está disponible en GHCR: | Entorno | Imagen | Etiqueta | -|-------------|-------|-----| +|---------|--------|----------| | Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | | Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | | Devnet (privada) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | @@ -418,48 +436,48 @@ sudo sysctl -w net.core.rmem_max=268435456 ## Límites y Contrapresión -| Límite | Valor predeterminado | Comportamiento cuando se excede | -|-------|---------|------------------------| +| Límite | Valor por defecto | Comportamiento cuando se excede | +|--------|-------------------|--------------------------------| | Clientes concurrentes (`WS_MAX_CLIENTS`) | 64 | La nueva conexión es rechazada. | | Suscripciones por cliente (`WS_MAX_SUBS`) | 256 | El `subscribe` es rechazado con un error. | | Mensajes de control entrantes / cliente / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | El cliente es desconectado. | -| Buffer de difusión (`WS_BROADCAST_CAPACITY`) | 4096 | Un cliente lento **descarta los mensajes más antiguos** (nunca detiene el feed). | +| Buffer de broadcast (`WS_BROADCAST_CAPACITY`) | 4096 | Un cliente lento **descarta los mensajes más antiguos** (nunca detiene el feed). | -Dado que cada `quote` y `depth` es estado completo, un consumidor que descarta mensajes bajo contrapresión se auto-recupera con el siguiente mensaje — no se requiere handshake de resincronización. +Dado que cada `quote` y `depth` es estado completo, un consumidor que pierde mensajes bajo contrapresión se autocorrige con el siguiente mensaje — no se requiere handshake de resincronización. --- -## Solución de Problemas +## Resolución de Problemas ### No llegan shreds al puerto local - Confirme que su acceso está autorizado para los grupos de shreds `edge-solana-*` onchain. - Verifique que el túnel esté activo: `sudo docker exec -it doublezero-edge-connect doublezero status` - Revise los logs en busca de errores de unión: `sudo docker logs -f doublezero-edge-connect` -- Confirme que `DZ_SHRED_FORWARD` apunta a un destino UDP local alcanzable. +- Confirme que `DZ_SHRED_FORWARD` apunta a un destino UDP local accesible. ### No hay mensajes de un venue - Verifique que el túnel esté activo: `sudo docker exec -it doublezero-edge-connect doublezero status` - Revise los logs en busca de errores de unión: `sudo docker logs -f doublezero-edge-connect` - Confirme que su acceso está autorizado para ese venue onchain. -- Acote la ingesta a ese venue con `DZ_FEEDS=` para aislar el problema. +- Limite la ingesta a ese venue con `DZ_FEEDS=` para aislar el problema. -### El WebSocket conecta pero no llegan cotizaciones +### El WebSocket se conecta pero no llegan cotizaciones -- Los mensajes `instrument` siempre llegan primero; las cotizaciones siguen una vez que se completa el handshake de datos de referencia. Espere 10–20 segundos después de conectar antes de concluir que faltan datos. +- Los mensajes `instrument` siempre llegan primero; las cotizaciones siguen una vez que el handshake de datos de referencia se completa. Espere 10–20 segundos después de conectar antes de concluir que faltan datos. - Verifique `dz_feed_up{venue}` en las métricas — `0` significa que el multicast está en silencio en su host. -- Verifique que las reglas del firewall permitan UDP multicast en la interfaz `doublezero1`. +- Verifique que las reglas de firewall permitan UDP multicast en la interfaz `doublezero1`. ### Alto `dz_ws_client_lagged_total` -Su consumidor está leyendo más lento de lo que el puente está publicando. Aumente el buffer de difusión con `WS_BROADCAST_CAPACITY`, reduzca el tiempo de procesamiento por mensaje, o agregue un hilo de lectura dedicado. +Su consumidor está leyendo más lento de lo que el puente está publicando. Aumente el buffer de broadcast con `WS_BROADCAST_CAPACITY`, reduzca el tiempo de procesamiento por mensaje, o agregue un hilo de lectura dedicado. -### El contenedor sale inmediatamente +### El contenedor se cierra inmediatamente -- El puente requiere `--network host` y el dispositivo `/dev/net/tun`; un `docker run` sin esos flags fallará. -- Use el one-liner del instalador o el comando `docker run` exacto mostrado en [Autoalojamiento](#avanzado-autoalojamiento). +- El puente requiere `--network host` y el dispositivo `/dev/net/tun`; un `docker run` simple sin esos flags fallará. +- Use el instalador de una línea o el comando `docker run` exacto mostrado en [Autoalojamiento](#avanzado-autoalojamiento). ### El túnel GRE no se establece -Consulte [Solución de problemas](troubleshooting.md) y asegúrese de que el protocolo IP 47 esté permitido en su proveedor de nube. En AWS, deshabilite la verificación de origen/destino del ENI para el host. \ No newline at end of file +Consulte [Resolución de problemas](troubleshooting.md) y asegúrese de que el protocolo IP 47 esté permitido en su proveedor de nube. En AWS, deshabilite la verificación source/dest del ENI para el host. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.fr.md b/docs/Edge Market Data Connection.fr.md index ea530d4..6ce4518 100644 --- a/docs/Edge Market Data Connection.fr.md +++ b/docs/Edge Market Data Connection.fr.md @@ -1,22 +1,22 @@ --- -description: Exécutez doublezero-edge-connect pour retransmettre les shreds Solana vers un port UDP local et consommer les données de marché normalisées Edge via un WebSocket local. +description: Exécutez doublezero-edge-connect pour retransmettre les shreds Solana vers un port UDP local et consommer des données de marché Edge normalisées via un WebSocket local. --- # Connexion Edge -!!! warning "En me connectant à DoubleZero, j'accepte les [Conditions d'utilisation de DoubleZero](https://doublezero.xyz/terms-protocol). Les données sont destinées à votre usage interne uniquement et ne peuvent pas être retransmises (voir Section 2(e))." +!!! warning "En me connectant à DoubleZero, j'accepte les [Conditions d'utilisation de DoubleZero](https://doublezero.xyz/terms-protocol). Les données sont réservées à votre usage interne et ne peuvent pas être retransmises (voir Section 2(e))." `doublezero-edge-connect` est un pont qui rejoint le **multicast binaire DoubleZero Edge** et le redistribue localement sous forme de deux flux : -1. **Retransmission de shreds Solana** — shreds dédupliqués (avec vérification optionnelle de signature) distribués vers une ou plusieurs destinations UDP locales, prêts pour votre validateur ou RPC. -2. **Données de marché normalisées** — flux des venues Edge décodés, avec correction de précision, et redistribués sous forme d'un WebSocket JSON unique sur `ws://host:8081`. +1. **Retransmission des shreds Solana** — shreds dédupliquées (avec vérification de signature optionnelle) distribuées vers une ou plusieurs destinations UDP locales, prêtes pour votre validateur ou RPC. +2. **Données de marché normalisées** — flux des plateformes Edge décodés, corrigés en précision, et redistribués sous forme d'un WebSocket JSON unique sur `ws://host:8081`. -Les deux fonctionnent depuis le même conteneur et la même installation en une seule ligne. Activez les flux que votre autorisation onchain vous accorde. +Les deux fonctionnent depuis le même conteneur et la même installation en une ligne. Activez les flux que votre autorisation onchain vous accorde. ``` - ┌─ UDP datagrams ──▶ validateur / RPC + ┌─ UDP datagrams ──▶ validator / RPC DZ Edge multicast ──▶ doublezero-edge-connect ─┤ - (binaire) (dedup · décodage · normalisation) └─ WebSocket (JSON) ──▶ moteur de trading + (binary) (dedup · decode · normalize) └─ WebSocket (JSON) ──▶ trading engine ws://host:8081 ``` @@ -25,15 +25,15 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## Prérequis - Hôte **Linux/amd64** avec une adresse IPv4 publique autorisée onchain pour l'environnement cible. -- **Docker** (l'installation en une ligne l'installe s'il est absent). +- **Docker** (l'installateur en une ligne l'installe s'il est absent). - **Connectivité GRE** — autorisez le protocole IP 47 chez votre fournisseur cloud ; sur AWS, désactivez la vérification source/dest de l'ENI. -- Un **secret d'accès DoubleZero** : un jeton base64 préfixé `DZ_` ou un chemin vers un fichier keypair, obtenu lors du processus d'[onboarding DoubleZero](setup.md). +- Un **secret d'accès DoubleZero** : un jeton base64 préfixé `DZ_` ou un chemin vers un fichier de clés, obtenu lors du processus d'[intégration DoubleZero](setup.md). --- -## Étape 1 : Installation et exécution +## Étape 1 : Installer et Exécuter -Une seule commande prépare l'hôte et démarre le conteneur pont. Il rejoint le réseau DoubleZero et démarre chaque flux que votre autorisation accorde — retransmission de shreds et/ou WebSocket de données de marché sur `:8081` : +Une seule commande prépare l'hôte et démarre le conteneur pont. Il rejoint le réseau DoubleZero et démarre chaque flux que votre autorisation accorde — retransmission des shreds et/ou le WebSocket de données de marché sur `:8081` : === "Mainnet-beta" @@ -56,17 +56,17 @@ Une seule commande prépare l'hôte et démarre le conteneur pont. Il rejoint le Ce que fait le script : -1. Vérifie que l'hôte est Linux/amd64, s'assure que Docker est présent (propose de l'installer). -2. Prépare le noyau de l'hôte pour le tunnel GRE : charge `tun`/`ip_gre`, augmente `net.core.rmem_max`, avertit concernant les règles de pare-feu et du fournisseur cloud. -3. Charge votre secret d'accès (demandé une fois si `DZ_SECRET` n'est pas défini). -4. Lance le conteneur pont (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) et exécute `doublezero connect multicast`. +1. Vérifie que l'hôte est Linux/amd64. +2. Charge votre secret d'accès (demandé une fois si `DZ_SECRET` n'est pas défini) et **vérifie son pass d'accès onchain avant d'installer quoi que ce soit** — une vérification côté hôte pure contre le JSON-RPC public du registre. Si le pass est lié à une IP différente de celle de l'hôte, il interrompt l'opération (lorsque l'IP a été donnée explicitement via `DZ_CLIENT_IP`) ou émet un avertissement et continue (lorsque l'IP a été auto-détectée, ce qui peut être erroné derrière un NAT), laissant `doublezero connect` comme vérification réelle. +3. S'assure que Docker est présent (propose de l'installer) et prépare le noyau de l'hôte pour le tunnel GRE : charge `tun`/`ip_gre`, augmente `net.core.rmem_max`, avertit concernant le pare-feu et les règles du fournisseur cloud. +4. Exécute le conteneur pont (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) et lance `doublezero connect multicast`. !!! tip "Installation non interactive" - Définissez `DZ_SECRET=DZ_…` avant le pipe pour une exécution entièrement automatique — aucune invite. + Définissez `DZ_SECRET=DZ_…` avant le pipe pour exécuter de manière entièrement automatisée — aucune invite. --- -## Étape 2 : Configuration +## Étape 2 : Configurer Toute la configuration se fait via des **variables d'environnement définies avant le pipe**. Il n'y a pas de fichier de configuration. @@ -78,29 +78,34 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash | Variable | Défaut | Objectif | |----------|--------|----------| -| `DZ_SECRET` | *(demandé)* | Jeton base64 préfixé `DZ_` **ou** chemin vers un fichier keypair. Un jeton est injecté dans le conteneur et n'est jamais écrit sur le disque ; un fichier est monté en bind en lecture seule. | -| `DZ_ENV` | selon le script | `mainnet-beta` \| `testnet` \| `devnet`. | -| `DZ_IMAGE` | selon le script | Remplacer l'image du conteneur. | +| `DZ_SECRET` | *(demandé)* | Jeton base64 préfixé `DZ_` **ou** chemin vers un fichier de clés. Un jeton est injecté dans le conteneur et jamais écrit sur disque ; un fichier est monté en lecture seule. | +| `DZ_ENV` | par script | `mainnet-beta` \| `testnet` \| `devnet`. | +| `DZ_IMAGE` | par script | Remplacer l'image du conteneur. | | `DZ_NAME` | `doublezero-edge-connect` | Nom du conteneur. | -| `DZ_FEEDS` | *(tous)* | Venues séparées par des virgules pour restreindre l'ingestion de données de marché (ex. `VenueA,VenueB`). N'affecte pas la retransmission de shreds Solana. | +| `DZ_FEEDS` | *(tous)* | Plateformes séparées par des virgules pour restreindre l'ingestion des données de marché (ex. `VenueA,VenueB`). N'affecte pas la retransmission des shreds Solana. | +| `DZ_CLIENT_IP` | *(auto-détecté)* | Remplacer l'IPv4 publique utilisée par la pré-vérification du pass d'accès onchain. Définissez-la lorsque l'auto-détection est erronée (ex. derrière un NAT) afin que la pré-vérification puisse confirmer plutôt que simplement avertir. | +| `DZ_LEDGER_RPC_URL` | par env | Remplacer le point de terminaison RPC du registre DoubleZero utilisé par la pré-vérification. | | `DZ_ASSUME_YES` | `0` | Ignorer les invites de confirmation (ex. l'invite d'installation de Docker). | | `DZ_GHCR_TOKEN` | — | **Devnet uniquement** — un jeton GHCR avec `read:packages` (l'image devnet est privée). | | `DZ_GHCR_USER` | `malbeclabs` | **Devnet uniquement** — nom d'utilisateur GHCR pour la connexion. | ### Variables du pont -L'installateur transmet directement **toute variable de pont non vide** au conteneur. Les plus courantes : +L'installateur transmet **toute variable non vide** du pont directement au conteneur. Les plus courantes : | Variable | Défaut | Objectif | |----------|--------|----------| | `DZ_IFACE` | `doublezero1` | Interface réseau d'écoute. | -| `DZ_RECV_BUF` | — | Remplacement du tampon de réception UDP (octets). | -| `METRICS_BIND` | *(vide / désactivé)* | Activer le endpoint Prometheus `/metrics` (ex. `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | Niveau de log (`debug`, `warn`, etc.). | -| `DZ_SHRED_FORWARD` | — | Destination(s) UDP locale(s) pour les shreds retransmis — voir [Retransmission de shreds Solana](#retransmission-de-shreds-solana). | -| `WS_BIND` | `0.0.0.0:8081` | Adresse de liaison du WebSocket de données de marché — voir [WebSocket de données de marché](#websocket-de-donnees-de-marche). | +| `DZ_RECV_BUF` | `8388608` | Remplacement du tampon de réception UDP (octets ; défaut 8 Mio). | +| `METRICS_BIND` | *(vide / désactivé)* | Activer le point de terminaison Prometheus `/metrics` (ex. `127.0.0.1:9090`). | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | Niveau de journalisation (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destination(s) UDP locale(s) pour les shreds retransmises — voir [Retransmission des Shreds Solana](#retransmission-des-shreds-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Adresse de liaison du WebSocket de données de marché — voir [WebSocket de Données de Marché](#websocket-de-données-de-marché). | | `WS_MAX_CLIENTS` | `64` | Nombre maximum de clients WebSocket simultanés. | -| `WS_INPUT_COINS` | *(vide / désactivé)* | Activer le WebSocket public de secours pour les symboles listés (ex. `BTC,ETH`). | +| `WS_INPUT_COINS` | *(vide / désactivé)* | Activer le WebSocket public Hyperliquid comme solution de secours pour les symboles listés (ex. `BTC,ETH`) — voir [Sources d'entrée](#sources-dentrée-et-solution-de-secours-websocket). | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | URL du WebSocket public Hyperliquid pour la solution de secours. | +| `PHOENIX_WS_INPUT_MARKETS` | *(vide / désactivé)* | Activer le WebSocket public Phoenix comme solution de secours (trades uniquement) pour les tickers listés (ex. `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | URL du WebSocket public Phoenix pour la solution de secours. | **Exemples :** @@ -112,26 +117,26 @@ DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ # Non interactif, testnet : DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# Restreindre les données de marché à des venues spécifiques, logs verbeux, port WS non standard : +# Restreindre les données de marché à des plateformes spécifiques, journalisation détaillée, port WS non standard : DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# Activer les métriques et un WS public de secours : +# Activer les métriques et une solution de secours WS publique : DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ curl -fsSL https://get.doublezero.xyz/connect | bash ``` !!! note - Parce que l'installateur ne transmet que les valeurs **non vides**, vous ne pouvez pas passer un remplacement vide (ex. `WS_BIND=""` pour désactiver le sink WebSocket) via la commande en une ligne. Utilisez un `docker run` écrit manuellement pour cela — voir [Auto-hébergement](#avance-auto-hebergement). + L'installateur ne transmet que les valeurs **non vides**, avec une exception : `WS_BIND` est transmis même lorsqu'il est défini vide, donc `WS_BIND=""` **désactive bien** le sink WebSocket via l'installateur en une ligne. Pour toute autre variable, un remplacement vide ne peut pas être transmis via le pipe — utilisez un `docker run` écrit manuellement pour cela (voir [Auto-hébergement](#avancé--auto-hébergement)). --- -## Retransmission de shreds Solana +## Retransmission des Shreds Solana -Le pont rejoint les groupes multicast de shreds `edge-solana-*` et distribue chaque datagramme vers une ou plusieurs destinations UDP locales — alimentant votre validateur ou RPC directement depuis le réseau Edge. Il s'active automatiquement à la découverte lorsque ces groupes sont présents dans votre autorisation. +Le pont rejoint les groupes multicast de shreds `edge-solana-*` et distribue chaque datagramme vers une ou plusieurs destinations UDP locales — alimentant votre validateur ou RPC directement depuis le réseau Edge. Il s'active automatiquement lors de la découverte lorsque ces groupes sont présents dans votre autorisation. ```bash -# Par défaut (dédup uniquement, retransmission vers le port local 20000) : +# Par défaut (déduplication uniquement, retransmission vers le port local 20000) : DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash # Avec vérification de signature : @@ -143,31 +148,35 @@ DZ_SHRED_DEDUP_MODE=sigverify \ | Variable | Défaut | Objectif | |----------|--------|----------| -| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destination(s) pour les shreds retransmis (répétable). | +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destination(s) pour les shreds retransmises (répétable). | +| `DZ_SHRED_DISABLE` | `0` | Désactivation principale (`--shred-forward-disable`). Maintient le retransmetteur désactivé quelle que soit votre autorisation — définissez-le lorsqu'aucun consommateur local n'écoute, pour éviter de gaspiller du CPU à retransmettre le flux de shreds vers nulle part. | | `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (une copie par shred), `sigverify` (+ vérification ed25519), `none` (tous les datagrammes). | -| `DZ_SHRED_RPC_URL` | — | Endpoint RPC Solana ; requis par le mode `sigverify`. | +| `DZ_SHRED_RPC_URL` | — | Point de terminaison RPC Solana ; requis pour le mode `sigverify`. | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Taille de la fenêtre de déduplication. | -Voir [Retransmission de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) pour le pipeline complet et les mises en garde. +Voir [Retransmission des shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) pour le pipeline complet et les mises en garde. --- -## WebSocket de données de marché +## WebSocket de Données de Marché -Ouvrez un WebSocket vers `ws://:8081` et lisez les trames JSON. Vous recevez toutes les venues pour lesquelles vous êtes autorisé. Un message `subscribe` optionnel permet de restreindre le flux à des venues et symboles spécifiques. +Ouvrez un WebSocket vers `ws://:8081` et lisez les trames JSON. Vous recevez toutes les plateformes pour lesquelles vous êtes autorisé. Un message `subscribe` optionnel permet de restreindre le flux à des plateformes et symboles spécifiques. -Tout moteur compatible WebSocket + JSON peut le consommer avec un adaptateur léger (~50–100 lignes). Le multicast binaire, la séparation deux-ports par venue, et le handshake manifeste/précision restent tous à l'intérieur du pont ; le seul contrat contre lequel un consommateur doit coder est le WebSocket JSON. +Tout moteur qui parle WebSocket + JSON peut le consommer avec un adaptateur léger (~50–100 lignes). Le multicast binaire, la séparation en deux ports par plateforme, et le handshake manifeste/précision restent tous à l'intérieur du pont ; le seul contrat qu'un consommateur implémente est le WebSocket JSON. + +!!! note + Le sink WebSocket ne démarre que lorsqu'au moins un flux de données de marché est actif pour votre autorisation — un hôte dédié aux shreds uniquement ne sert aucun WebSocket. L'activation est pilotée par un réconciliateur d'abonnements onchain qui se rafraîchit toutes les 30 secondes (`--subscription-refresh-secs`) ; `--subscription-gating-disable` permet de désactiver le contrôle d'accès. ### Cycle de vie de la connexion À chaque nouvelle connexion, le pont : 1. **Rejoue les définitions d'instruments actuelles** — un message `instrument` par symbole connu — afin que le consommateur dispose de la précision avant la première cotation. -2. **Rejoue le dernier snapshot de profondeur** par symbole (si le flux Market-by-Order est actif). -3. **Diffuse** les messages `quote` / `trade` / `midpoint` / `depth` au fur et à mesure de leur arrivée, distribués à tous les consommateurs connectés. +2. **Rejoue le dernier instantané de profondeur** par symbole (si le flux Market-by-Order est actif). +3. **Diffuse en continu** les messages `quote` / `trade` / `midpoint` / `depth` à mesure qu'ils arrivent, distribués à tous les consommateurs connectés. ``` -connexion → instrument (×N) → depth (×M, derniers carnets) → quote → trade → depth → … +connect → instrument (×N) → depth (×M, derniers carnets) → quote → trade → depth → … ``` ### Types de messages @@ -177,11 +186,11 @@ Chaque message est un objet JSON identifié par un champ `type` : | `type` | Signification | |--------|---------------| | `instrument` | Définition d'instrument/précision. | -| `quote` | Mise à jour du meilleur achat/vente (état complet). | -| `trade` | Impression de transaction (dernière vente). | -| `midpoint` | Prix milieu dérivé. | -| `depth` | Snapshot complet de la profondeur du carnet d'ordres. | -| `status` | Transition de santé du flux au niveau de la venue. | +| `quote` | Mise à jour du meilleur niveau (état complet). | +| `trade` | Transaction exécutée (dernière vente). | +| `midpoint` | Prix médian dérivé. | +| `depth` | Instantané complet de la profondeur du carnet d'ordres. | +| `status` | Transition de l'état de santé du flux au niveau de la plateforme. | Les consommateurs **doivent ignorer les valeurs `type` inconnues et les champs inconnus** (compatibilité ascendante). @@ -191,7 +200,7 @@ Les consommateurs **doivent ignorer les valeurs `type` inconnues et les champs i {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -Envoyé à la connexion et à chaque modification des définitions. `price_exponent` et `qty_exponent` donnent le pas de cotation et le pas de taille de la venue sous forme de puissances de dix. +Envoyé à la connexion et chaque fois que les définitions changent. `price_exponent` et `qty_exponent` donnent le pas de cotation et l'incrément de taille de la plateforme sous forme de puissances de dix. #### `quote` @@ -214,7 +223,7 @@ Chaque `quote` est un **état complet** — un message perdu se corrige automati ``` source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (réception consommateur) - carnet venue arrivée fil post-décodage transfert WS + carnet plateforme arrivée réseau post-décodage transfert WS ``` `0` est la valeur sentinelle pour « non disponible » — traitez-la comme manquante, pas comme 1970. @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (réceptio } ``` -`aggressor_side` est `"buy"`, `"sell"`, ou `"unknown"`. Les transactions sont des événements ponctuels et ne sont pas rejouées à la reconnexion. +`aggressor_side` vaut `"buy"`, `"sell"`, ou `"unknown"`. Les trades sont des événements ponctuels et ne sont pas rejoués lors de la reconnexion. #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (réceptio } ``` -Les `bids` sont triés du prix le plus élevé au plus bas ; les `asks` sont triés du prix le plus bas au plus élevé. Chaque `depth` est un **snapshot complet** — remplacez, ne fusionnez pas. +Les `bids` sont triés du prix le plus élevé au plus bas ; les `asks` sont triés du prix le plus bas au plus élevé. Chaque `depth` est un **instantané complet** — remplacez, ne fusionnez pas. #### `status` @@ -256,7 +265,7 @@ Les `bids` sont triés du prix le plus élevé au plus bas ; les `asks` sont tri {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -Émis en périphérie lorsque le multicast de cotations d'une venue devient silencieux (`state:"down"`) ou récupère (`state:"ok"`). Utilisez-le pour griser une venue dans votre interface. La livraison des cotations n'est pas conditionnée par le statut — le flux se corrige automatiquement à la prochaine cotation. +Émis à la périphérie lorsque le multicast de cotations d'une plateforme devient silencieux (`state:"down"`) ou reprend (`state:"ok"`). Utilisez-le pour griser une plateforme dans votre interface. La livraison des cotations n'est pas conditionnée par le statut — le flux se rétablit automatiquement à la prochaine cotation. ### Abonnements @@ -267,7 +276,7 @@ Par défaut, vous recevez tout. Envoyez un message de contrôle pour restreindre {"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -Omettre un champ correspond à toute valeur (`{"symbol":"SOL"}` = SOL sur chaque venue). `venue` est comparé sans tenir compte de la casse. +Omettre un champ correspond à toute valeur (`{"symbol":"SOL"}` = SOL sur toutes les plateformes). `venue` est comparé sans distinction de casse. **Accusé de réception du serveur :** @@ -275,12 +284,12 @@ Omettre un champ correspond à toute valeur (`{"symbol":"SOL"}` = SOL sur chaque {"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -Les erreurs retournent `{"channel":"error","error":""}`. +Les erreurs renvoient `{"channel":"error","error":""}`. ### Heartbeat et vivacité - Le serveur envoie un **Ping WebSocket** toutes les 20 secondes ; les clients conformes répondent automatiquement par un Pong. -- Les clients silencieux pendant 60 secondes sont fermés et supprimés. +- Les clients silencieux pendant 60 secondes sont fermés et nettoyés. - Keepalive au niveau applicatif : `{"method":"ping"}` → `{"channel":"pong"}`. ### Squelette de consommateur @@ -310,29 +319,37 @@ ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() ``` -### Sources d'entrée et WebSocket de secours +### Sources d'entrée et solution de secours WebSocket -Le flux multicast Edge est toujours actif. Un **WebSocket public de secours** optionnel peut combler les lacunes lorsque le flux Edge cale : +Le flux multicast Edge est permanent. Des **solutions de secours WebSocket publiques** optionnelles peuvent combler les lacunes lorsque le flux Edge s'interrompt. Deux sont disponibles, chacune désactivée par défaut et activée indépendamment par plateforme : + +| Solution de secours | Activation avec | Couvre | URL par défaut | +|---------------------|-----------------|--------|----------------| +| **Hyperliquid** | `WS_INPUT_COINS` (ex. `BTC,ETH`) | cotations + trades | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (tickers, ex. `SOL,BTC`) | **trades uniquement** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# Activer le secours pour BTC et ETH : +# Activer la solution de secours Hyperliquid pour BTC et ETH : WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Activer la solution de secours Phoenix pour les trades SOL : +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -Les deux sources sont en concurrence par tick `(venue, symbol, source_ts)` au sein d'un arbitre partagé. En régime permanent, la source Edge gagne (sub-ms contre des dizaines de ms via internet) ; lorsque l'Edge a des lacunes, la copie publique prend le relais. La sortie WebSocket est identique quel que soit la source ayant livré une mise à jour donnée. +Pour chaque tick `(venue, symbol, source_ts)`, les sources Edge et publiques sont en compétition au sein d'un arbitre partagé. En régime permanent, la source Edge l'emporte (sub-ms vs. dizaines de ms sur internet) ; quand Edge présente des lacunes, la copie publique prend le relais. La sortie WebSocket est identique quelle que soit la source ayant livré une mise à jour donnée. (Les solutions de secours Phoenix ne couvrent que les trades — Edge reste la seule source des cotations Phoenix.) --- -## Gestion du conteneur +## Gérer le Conteneur ```bash -# Diffuser les logs +# Diffuser les journaux sudo docker logs -f doublezero-edge-connect -# Vérifier le statut du tunnel +# Vérifier l'état du tunnel sudo docker exec -it doublezero-edge-connect doublezero status -# Vérifier les latences des appareils +# Vérifier les latences des interfaces sudo docker exec -it doublezero-edge-connect doublezero latency # Arrêter et supprimer @@ -340,13 +357,13 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne ``` !!! note "Pas de TLS" - Le pont cible un réseau de confiance/local. Terminez le TLS au niveau d'un reverse proxy si vous exposez le endpoint WebSocket à l'extérieur. + Le pont cible un réseau de confiance/local. Terminez le TLS au niveau d'un reverse proxy si vous exposez le point de terminaison WebSocket vers l'extérieur. --- -## Surveillance (Métriques Prometheus) +## Supervision (Métriques Prometheus) -Le endpoint de métriques est **désactivé par défaut**. Activez-le avec `METRICS_BIND` : +Le point de terminaison des métriques est **désactivé par défaut**. Activez-le avec `METRICS_BIND` : ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash @@ -362,12 +379,13 @@ Métriques clés : | Métrique | Ce qu'elle mesure | |----------|-------------------| -| `dz_feed_up{venue}` | `1` tant que le multicast de cette venue est actif, `0` lorsqu'il est silencieux. | -| `dz_datagrams_received_total{venue}` | Volume d'ingestion par venue. | +| `dz_feed_up{venue}` | `1` tant que le multicast de cette plateforme est actif, `0` tant qu'il est silencieux. | +| `dz_datagrams_received_total{venue}` | Volume d'ingestion par plateforme. | | `dz_emit_total{venue,kind}` | Messages diffusés après déduplication, par type. | +| `dz_quotes_admitted_total{venue,publisher}` | Cotations admises par l'arbitre, attribuées à la source gagnante. Une hausse de `publisher="public"` signifie qu'une solution de secours comble une lacune Edge (vs. `publisher="edge"` en régime permanent). | | `dz_quotes_dropped_total{venue}` | Cotations obsolètes/dupliquées supprimées. | | `dz_ws_clients` | Clients WebSocket actuellement connectés. | -| `dz_ws_messages_sent_total{kind}` | Messages transférés aux clients. | +| `dz_ws_messages_sent_total{kind}` | Messages transmis aux clients. | | `dz_ws_client_lagged_total` | Nombre de fois qu'un client lent a été éjecté pour protéger le flux. | Une sonde de vivacité `GET /healthz` est également servie sur la même adresse de liaison. @@ -379,12 +397,12 @@ Une sonde de vivacité `GET /healthz` est également servie sur la même adresse Le conteneur est disponible sur GHCR : | Environnement | Image | Tag | -|---------------|-------|-----| +|----------------|-------|-----| | Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | | Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | | Devnet (privé) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | -Lancez-le manuellement (nécessaire pour les options que l'installateur ne peut pas transmettre, comme `WS_BIND=""`) : +Exécutez-le manuellement (requis pour les options que l'installateur ne peut pas transmettre, comme `WS_BIND=""`) : ```bash docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ @@ -395,7 +413,7 @@ docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta ``` -**Compilation depuis les sources :** +**Compiler depuis les sources :** ```bash git clone https://github.com/malbeclabs/doublezero-edge-connect @@ -416,34 +434,34 @@ sudo sysctl -w net.core.rmem_max=268435456 --- -## Limites et contre-pression +## Limites et Contre-pression | Limite | Défaut | Comportement en cas de dépassement | |--------|--------|------------------------------------| -| Clients simultanés (`WS_MAX_CLIENTS`) | 64 | La nouvelle connexion est refusée. | +| Clients simultanés (`WS_MAX_CLIENTS`) | 64 | La nouvelle connexion est rejetée. | | Abonnements par client (`WS_MAX_SUBS`) | 256 | Le `subscribe` est refusé avec une erreur. | | Messages de contrôle entrants / client / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Le client est déconnecté. | | Tampon de diffusion (`WS_BROADCAST_CAPACITY`) | 4096 | Un client lent **perd les messages les plus anciens** (ne bloque jamais le flux). | -Parce que chaque `quote` et `depth` est un état complet, un consommateur qui perd des messages sous contre-pression se corrige automatiquement au prochain message — aucun handshake de resynchronisation requis. +Parce que chaque `quote` et `depth` est un état complet, un consommateur qui perd des messages sous contre-pression se rétablit automatiquement au message suivant — aucun handshake de resynchronisation requis. --- ## Dépannage -### Aucun shred n'arrive au port local +### Aucune shred n'arrive sur le port local - Confirmez que votre accès est autorisé pour les groupes de shreds `edge-solana-*` onchain. - Vérifiez que le tunnel est actif : `sudo docker exec -it doublezero-edge-connect doublezero status` -- Vérifiez les erreurs de jointure dans les logs : `sudo docker logs -f doublezero-edge-connect` +- Consultez les journaux pour les erreurs de jonction : `sudo docker logs -f doublezero-edge-connect` - Confirmez que `DZ_SHRED_FORWARD` pointe vers une destination UDP locale accessible. -### Aucun message d'une venue +### Aucun message provenant d'une plateforme - Vérifiez que le tunnel est actif : `sudo docker exec -it doublezero-edge-connect doublezero status` -- Vérifiez les erreurs de jointure dans les logs : `sudo docker logs -f doublezero-edge-connect` -- Confirmez que votre accès est autorisé pour cette venue onchain. -- Restreignez l'ingestion à cette venue avec `DZ_FEEDS=` pour isoler le problème. +- Consultez les journaux pour les erreurs de jonction : `sudo docker logs -f doublezero-edge-connect` +- Confirmez que votre accès est autorisé pour cette plateforme onchain. +- Restreignez l'ingestion à cette plateforme avec `DZ_FEEDS=` pour isoler le problème. ### Le WebSocket se connecte mais aucune cotation n'arrive @@ -451,15 +469,6 @@ Parce que chaque `quote` et `depth` est un état complet, un consommateur qui pe - Vérifiez `dz_feed_up{venue}` dans les métriques — `0` signifie que le multicast est silencieux sur votre hôte. - Vérifiez que les règles de pare-feu autorisent le multicast UDP sur l'interface `doublezero1`. -### `dz_ws_client_lagged_total` élevé - -Votre consommateur lit plus lentement que le pont ne publie. Augmentez le tampon de diffusion avec `WS_BROADCAST_CAPACITY`, réduisez le temps de traitement par message, ou ajoutez un thread de lecture dédié. - -### Le conteneur se ferme immédiatement - -- Le pont nécessite `--network host` et le périphérique `/dev/net/tun` ; un simple `docker run` sans ces flags échouera. -- Utilisez la commande d'installation en une ligne ou la commande `docker run` exacte indiquée dans [Auto-hébergement](#avance-auto-hebergement). - -### Le tunnel GRE ne s'établit pas +### Valeur élevée de `dz_ws_client_lagged_total` -Consultez [Dépannage](troubleshooting.md) et assurez-vous que le protocole IP 47 est autorisé chez votre fournisseur cloud. Sur AWS, désactivez la vérification source/dest de l'ENI pour l'hôte. \ No newline at end of file +Votre consommateur lit plus lentement que le pont ne publie. Aug \ No newline at end of file diff --git a/docs/Edge Market Data Connection.it.md b/docs/Edge Market Data Connection.it.md index e26c6ce..4d7d105 100644 --- a/docs/Edge Market Data Connection.it.md +++ b/docs/Edge Market Data Connection.it.md @@ -1,17 +1,17 @@ --- -description: Esegui doublezero-edge-connect per ri-inoltrare gli shred di Solana verso una porta UDP locale e consumare dati di mercato Edge normalizzati tramite un WebSocket locale. +description: Esegui doublezero-edge-connect per ri-inoltrare gli shred di Solana a una porta UDP locale e consumare dati di mercato Edge normalizzati tramite un WebSocket locale. --- # Connessione Edge -!!! warning "Connettendomi a DoubleZero accetto i [Termini di utilizzo di DoubleZero](https://doublezero.xyz/terms-protocol). I dati sono esclusivamente per uso interno e non possono essere ritrasmessi (vedi Sezione 2(e))." +!!! warning "Connettendomi a DoubleZero accetto i [Termini d'uso di DoubleZero](https://doublezero.xyz/terms-protocol). I dati sono esclusivamente per uso interno e non possono essere ritrasmessi (vedi Sezione 2(e))." -`doublezero-edge-connect` è un bridge che si connette al **multicast binario di DoubleZero Edge** e lo ri-serve localmente come due feed: +`doublezero-edge-connect` è un bridge che si unisce al **multicast binario di DoubleZero Edge** e lo ri-serve localmente come due feed: -1. **Inoltro degli shred Solana** — shred deduplicati (opzionalmente con verifica della firma) distribuiti a una o più destinazioni UDP locali, pronti per il tuo validator o RPC. -2. **Dati di mercato normalizzati** — feed dei venue Edge decodificati, corretti in precisione e ri-serviti come singolo WebSocket JSON su `ws://host:8081`. +1. **Inoltro shred di Solana** — shred deduplicati (con verifica opzionale della firma) distribuiti a una o più destinazioni UDP locali, pronti per il tuo validator o RPC. +2. **Dati di mercato normalizzati** — feed dei venue Edge decodificati, con precisione corretta, e ri-serviti come un singolo WebSocket JSON su `ws://host:8081`. -Entrambi vengono eseguiti dallo stesso container e dalla stessa installazione con un singolo comando. Abilita i feed consentiti dalla tua autorizzazione onchain. +Entrambi vengono eseguiti dallo stesso container e dalla stessa installazione a riga singola. Abilita qualsiasi feed concesso dalla tua autorizzazione onchain. ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -24,16 +24,16 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## Requisiti -- Host **Linux/amd64** con un indirizzo IPv4 pubblico autorizzato onchain per l'ambiente target. -- **Docker** (il comando one-liner lo installa se mancante). -- **Connettività GRE** — consenti il protocollo IP 47 presso il tuo cloud provider; su AWS disabilita il controllo source/dest dell'ENI. -- Un **secret di accesso DoubleZero**: un token base64 con prefisso `DZ_` oppure un percorso a un file keypair, ottenuto dal processo di [onboarding DoubleZero](setup.md). +- Host **Linux/amd64** con un indirizzo IPv4 pubblico autorizzato onchain per l'ambiente di destinazione. +- **Docker** (l'installazione a riga singola lo installa se mancante). +- **Connettività GRE** — consentire il protocollo IP 47 presso il tuo cloud provider; su AWS disabilitare il controllo source/dest dell'ENI. +- Un **secret di accesso DoubleZero**: un token base64 con prefisso `DZ_` o un percorso a un file keypair, ottenuto dal processo di [onboarding DoubleZero](setup.md). --- ## Passo 1: Installazione ed Esecuzione -Un singolo comando prepara l'host e avvia il container bridge. Si connette alla rete DoubleZero e avvia ogni feed consentito dalla tua autorizzazione — inoltro shred e/o il WebSocket per i dati di mercato sulla porta `:8081`: +Un singolo comando prepara l'host e avvia il container bridge. Si unisce alla rete DoubleZero e avvia ogni feed concesso dalla tua autorizzazione — inoltro shred e/o il WebSocket per dati di mercato sulla porta `:8081`: === "Mainnet-beta" @@ -56,19 +56,19 @@ Un singolo comando prepara l'host e avvia il container bridge. Si connette alla Cosa fa lo script: -1. Verifica che l'host sia Linux/amd64, assicura che Docker sia presente (propone l'installazione se assente). -2. Prepara il kernel dell'host per il tunnel GRE: carica `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avvisa riguardo alle regole del firewall e del cloud provider. -3. Carica il tuo secret di accesso (richiesto una sola volta se `DZ_SECRET` non è impostato). -4. Esegue il container bridge (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) e lancia `doublezero connect multicast`. +1. Verifica che l'host sia Linux/amd64. +2. Carica il tuo secret di accesso (richiesto una volta se `DZ_SECRET` non è impostato) e **verifica il pass di accesso onchain prima di installare qualsiasi cosa** — un controllo puramente lato host contro il JSON-RPC pubblico del ledger. Se il pass è associato a un IP diverso da quello dell'host, interrompe l'esecuzione (quando l'IP è stato fornito esplicitamente tramite `DZ_CLIENT_IP`) o avvisa e continua (quando l'IP è stato solo auto-rilevato, il che può essere errato dietro NAT), lasciando `doublezero connect` come verifica effettiva. +3. Assicura che Docker sia presente (propone di installarlo) e prepara il kernel dell'host per il tunnel GRE: carica `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avvisa riguardo firewall e regole del cloud provider. +4. Esegue il container bridge (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) ed esegue `doublezero connect multicast`. !!! tip "Installazione non interattiva" - Imposta `DZ_SECRET=DZ_…` prima del pipe per eseguire in modo completamente automatico — nessun prompt. + Imposta `DZ_SECRET=DZ_…` prima della pipe per eseguire in modo completamente automatico — nessun prompt. --- ## Passo 2: Configurazione -Tutta la configurazione avviene tramite **variabili d'ambiente impostate prima del pipe**. Non esiste un file di configurazione. +Tutta la configurazione avviene tramite **variabili d'ambiente impostate prima della pipe**. Non esiste un file di configurazione. ```bash DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash @@ -77,58 +77,63 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash ### Variabili dell'installer | Variabile | Default | Scopo | -|-----------|---------|-------| -| `DZ_SECRET` | *(richiesto interattivamente)* | Token base64 con prefisso `DZ_` **oppure** percorso a un file keypair. Un token viene iniettato nel container e non viene mai scritto su disco; un file viene montato in sola lettura tramite bind mount. | +|----------|---------|---------| +| `DZ_SECRET` | *(richiesto)* | Token base64 con prefisso `DZ_` **oppure** percorso a un file keypair. Un token viene iniettato nel container e non viene mai scritto su disco; un file viene montato in bind read-only. | | `DZ_ENV` | per script | `mainnet-beta` \| `testnet` \| `devnet`. | -| `DZ_IMAGE` | per script | Sovrascrive l'immagine del container. | +| `DZ_IMAGE` | per script | Override dell'immagine del container. | | `DZ_NAME` | `doublezero-edge-connect` | Nome del container. | -| `DZ_FEEDS` | *(tutti)* | Venue separati da virgola per restringere l'ingestione dei dati di mercato (es. `VenueA,VenueB`). Non influisce sull'inoltro degli shred Solana. | +| `DZ_FEEDS` | *(tutti)* | Venue separati da virgola per restringere l'ingestione dei dati di mercato (es. `VenueA,VenueB`). Non influisce sull'inoltro degli shred di Solana. | +| `DZ_CLIENT_IP` | *(auto-rilevato)* | Override dell'IPv4 pubblico utilizzato dal pre-check del pass di accesso onchain. Impostalo quando l'auto-rilevamento è errato (es. dietro NAT) così il pre-check può confermare anziché solo avvisare. | +| `DZ_LEDGER_RPC_URL` | per env | Override dell'endpoint RPC del ledger DoubleZero utilizzato dal pre-check. | | `DZ_ASSUME_YES` | `0` | Salta i prompt di conferma (es. il prompt di installazione Docker). | | `DZ_GHCR_TOKEN` | — | **Solo Devnet** — un token GHCR con `read:packages` (l'immagine devnet è privata). | | `DZ_GHCR_USER` | `malbeclabs` | **Solo Devnet** — username GHCR per il login. | ### Variabili del bridge -L'installer inoltra **qualsiasi** variabile bridge non vuota direttamente al container. Le più comuni: +L'installer inoltra **qualsiasi variabile non vuota** del bridge direttamente al container. Le più comuni: | Variabile | Default | Scopo | -|-----------|---------|-------| -| `DZ_IFACE` | `doublezero1` | Interfaccia di rete su cui mettersi in ascolto. | -| `DZ_RECV_BUF` | — | Override del buffer di ricezione UDP (in byte). | +|----------|---------|---------| +| `DZ_IFACE` | `doublezero1` | Interfaccia di rete su cui ascoltare. | +| `DZ_RECV_BUF` | `8388608` | Override del buffer di ricezione UDP (byte; default 8 MiB). | | `METRICS_BIND` | *(vuoto / disattivato)* | Abilita l'endpoint Prometheus `/metrics` (es. `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | Livello di log (`debug`, `warn`, ecc.). | -| `DZ_SHRED_FORWARD` | — | Destinazione/i UDP locale/i per gli shred inoltrati — vedi [Inoltro Shred Solana](#inoltro-shred-solana). | -| `WS_BIND` | `0.0.0.0:8081` | Indirizzo di bind del WebSocket per i dati di mercato — vedi [WebSocket Dati di Mercato](#websocket-dati-di-mercato). | -| `WS_MAX_CLIENTS` | `64` | Numero massimo di client WebSocket simultanei. | -| `WS_INPUT_COINS` | *(vuoto / disattivato)* | Abilita il backstop WebSocket pubblico per i simboli elencati (es. `BTC,ETH`). | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | Livello di log (`debug`, `warn`, ecc.). | +| `DZ_SHRED_FORWARD` | — | Destinazione/i UDP locale/i per gli shred inoltrati — vedi [Inoltro Shred di Solana](#inoltro-shred-di-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Indirizzo di bind del WebSocket per dati di mercato — vedi [WebSocket Dati di Mercato](#websocket-dati-di-mercato). | +| `WS_MAX_CLIENTS` | `64` | Massimo numero di client WebSocket concorrenti. | +| `WS_INPUT_COINS` | *(vuoto / disattivato)* | Abilita il backstop WebSocket pubblico di Hyperliquid per i simboli listati (es. `BTC,ETH`) — vedi [Sorgenti di input](#sorgenti-di-input-e-backstop-websocket). | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | URL del WebSocket pubblico di Hyperliquid per il backstop. | +| `PHOENIX_WS_INPUT_MARKETS` | *(vuoto / disattivato)* | Abilita il backstop WebSocket pubblico di Phoenix (solo trade) per i ticker listati (es. `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | URL del WebSocket pubblico di Phoenix per il backstop. | **Esempi:** ```bash -# Inoltra gli shred a un validator/RPC locale: +# Inoltra shred a un validator/RPC locale: DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ curl -fsSL https://get.doublezero.xyz/connect | bash # Non interattivo, testnet: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# Restringi i dati di mercato a venue specifici, logging verboso, porta WS non predefinita: +# Restringi i dati di mercato a venue specifici, logging verbose, porta WS non predefinita: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# Abilita le metriche e un backstop WS pubblico: +# Abilita metriche e un backstop WS pubblico: DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ curl -fsSL https://get.doublezero.xyz/connect | bash ``` !!! note - Poiché l'installer inoltra solo valori **non vuoti**, non è possibile passare un override vuoto (es. `WS_BIND=""` per disabilitare il sink WebSocket) tramite il one-liner. Usa un `docker run` scritto manualmente per questo — vedi [Self-hosting](#avanzato-self-hosting). + L'installer inoltra solo valori **non vuoti**, con un'eccezione: `WS_BIND` viene inoltrato anche se impostato vuoto, quindi `WS_BIND=""` **disabilita** effettivamente il sink WebSocket tramite la riga singola. Per qualsiasi altra variabile, un override vuoto non può essere passato tramite la pipe — usa un `docker run` scritto manualmente per questo (vedi [Self-hosting](#avanzato-self-hosting)). --- -## Inoltro Shred Solana +## Inoltro Shred di Solana -Il bridge si unisce ai gruppi multicast `edge-solana-*` per gli shred e inoltra ogni datagramma a una o più destinazioni UDP locali — alimentando direttamente il tuo validator o RPC dalla rete Edge. Si attiva automaticamente al discovery quando quei gruppi sono presenti nella tua autorizzazione. +Il bridge si unisce ai gruppi multicast `edge-solana-*` per gli shred e distribuisce ogni datagramma a una o più destinazioni UDP locali — alimentando il tuo validator o RPC direttamente dalla rete Edge. Si attiva automaticamente al discovery quando quei gruppi sono presenti nella tua autorizzazione. ```bash # Default (solo dedup, inoltro alla porta locale 20000): @@ -142,10 +147,11 @@ DZ_SHRED_DEDUP_MODE=sigverify \ ``` | Variabile | Default | Scopo | -|-----------|---------|-------| +|----------|---------|---------| | `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destinazione/i per gli shred inoltrati (ripetibile). | +| `DZ_SHRED_DISABLE` | `0` | Opt-out principale (`--shred-forward-disable`). Mantiene il forwarder disattivato indipendentemente da ciò che concede la tua autorizzazione — impostalo quando nessun consumer locale è in ascolto, per evitare di sprecare CPU inoltrando il firehose di shred nel vuoto. | | `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (una copia per shred), `sigverify` (+ verifica ed25519), `none` (tutti i datagrammi). | -| `DZ_SHRED_RPC_URL` | — | Endpoint Solana RPC; richiesto dalla modalità `sigverify`. | +| `DZ_SHRED_RPC_URL` | — | Endpoint RPC Solana; richiesto dalla modalità `sigverify`. | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Dimensione della finestra di deduplicazione. | Vedi [Inoltro shred](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) per la pipeline completa e le avvertenze. @@ -156,31 +162,34 @@ Vedi [Inoltro shred](https://github.com/malbeclabs/doublezero-edge-connect/blob/ Apri un WebSocket verso `ws://:8081` e leggi frame JSON. Ricevi tutti i venue per cui sei autorizzato. Un messaggio opzionale `subscribe` restringe lo stream a venue e simboli specifici. -Qualsiasi engine che parli WebSocket + JSON può consumarlo con un adapter leggero (~50–100 righe). Il multicast binario, la suddivisione a due porte per venue e l'handshake manifest/precisione restano tutti all'interno del bridge; l'unico contratto su cui il consumer deve implementare è il WebSocket JSON. +Qualsiasi engine che parli WebSocket + JSON può consumarlo con un adapter sottile (~50–100 righe). Il multicast binario, la suddivisione a due porte per venue e l'handshake manifest/precisione restano tutti all'interno del bridge; l'unico contratto contro cui un consumer programma è il WebSocket JSON. + +!!! note + Il sink WebSocket si attiva solo quando almeno un feed di dati di mercato è attivo per la tua autorizzazione — un host solo-shred non serve alcun WebSocket. L'attivazione è guidata da un riconciliatore di sottoscrizioni onchain che si aggiorna ogni 30s (`--subscription-refresh-secs`); `--subscription-gating-disable` disabilita il gating. ### Ciclo di vita della connessione Ad ogni nuova connessione il bridge: -1. **Riproduce le definizioni degli strumenti correnti** — un messaggio `instrument` per ogni simbolo noto — così il consumer ha le informazioni di precisione prima della prima quotazione. +1. **Riproduce le definizioni correnti degli strumenti** — un messaggio `instrument` per ogni simbolo noto — così il consumer ha la precisione prima della prima quotazione. 2. **Riproduce l'ultimo snapshot di profondità** per simbolo (se il feed Market-by-Order è attivo). -3. **Invia in streaming** messaggi `quote` / `trade` / `midpoint` / `depth` man mano che arrivano, distribuiti a tutti i consumer connessi. +3. **Trasmette in streaming** messaggi `quote` / `trade` / `midpoint` / `depth` man mano che arrivano, distribuiti a tutti i consumer connessi. ``` -connect → instrument (×N) → depth (×M, ultimi book) → quote → trade → depth → … +connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … ``` ### Tipi di messaggio -Ogni messaggio è un oggetto JSON identificato da un campo `type`: +Ogni messaggio è un oggetto JSON contrassegnato da un campo `type`: | `type` | Significato | -|--------|-------------| -| `instrument` | Definizione dello strumento/precisione. | +|--------|---------| +| `instrument` | Definizione strumento/precisione. | | `quote` | Aggiornamento top-of-book (stato completo). | -| `trade` | Stampa di trade (ultimo scambio). | -| `midpoint` | Prezzo medio derivato. | -| `depth` | Snapshot completo della profondità dell'order book. | +| `trade` | Stampa di trade (ultimo prezzo). | +| `midpoint` | Prezzo mid derivato. | +| `depth` | Snapshot completo della profondità del book degli ordini. | | `status` | Transizione dello stato di salute del feed a livello di venue. | I consumer **devono ignorare valori `type` sconosciuti e campi sconosciuti** (compatibilità in avanti). @@ -191,7 +200,7 @@ I consumer **devono ignorare valori `type` sconosciuti e campi sconosciuti** (co {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -Inviato alla connessione e ogni volta che le definizioni cambiano. `price_exponent` e `qty_exponent` indicano il tick size e lo step di dimensione del venue come potenze di dieci. +Inviato alla connessione e ogni volta che le definizioni cambiano. `price_exponent` e `qty_exponent` forniscono il tick size e lo step di dimensione del venue come potenze di dieci. #### `quote` @@ -210,14 +219,14 @@ Inviato alla connessione e ogni volta che le definizioni cambiano. `price_expone } ``` -Ogni `quote` è **stato completo** — un messaggio perso si auto-ripristina con la quotazione successiva, nessuna risincronizzazione necessaria. I quattro timestamp decompongono la latenza end-to-end: +Ogni `quote` è **stato completo** — un messaggio perso si auto-ripara alla prossima quotazione, nessuna risincronizzazione necessaria. I quattro timestamp decompongono la latenza end-to-end: ``` -source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (ricezione consumer) - book del venue arrivo sul wire post-decode hand-off WS +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) + venue book wire arrival post-decode WS hand-off ``` -`0` è il valore sentinella per "non disponibile" — trattarlo come mancante, non come 1970. +`0` è il valore sentinella per "non disponibile" — trattalo come mancante, non come 1970. #### `trade` @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (ricezione } ``` -`aggressor_side` è `"buy"`, `"sell"` oppure `"unknown"`. I trade sono eventi puntuali e non vengono riprodotti alla riconnessione. +`aggressor_side` è `"buy"`, `"sell"` o `"unknown"`. I trade sono eventi puntuali e non vengono riprodotti alla riconnessione. #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (ricezione } ``` -I `bids` sono ordinati dal prezzo più alto al più basso; gli `asks` dal prezzo più basso al più alto. Ogni `depth` è uno **snapshot completo** — sostituire, non unire. +I `bids` sono ordinati dal prezzo più alto al più basso; gli `asks` sono ordinati dal prezzo più basso al più alto. Ogni `depth` è uno **snapshot completo** — sostituisci, non unire. #### `status` @@ -256,7 +265,7 @@ I `bids` sono ordinati dal prezzo più alto al più basso; gli `asks` dal prezzo {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -Emesso all'edge quando il multicast delle quotazioni di un venue diventa silenzioso (`state:"down"`) o si riprende (`state:"ok"`). Usalo per disattivare visualmente un venue nella tua UI. La consegna delle quotazioni non è vincolata allo stato — il feed si auto-ripristina con la quotazione successiva. +Emesso al limite quando il multicast delle quotazioni di un venue diventa silenzioso (`state:"down"`) o si riprende (`state:"ok"`). Usalo per disattivare visualmente un venue nella tua UI. La consegna delle quotazioni non è condizionata dallo status — il feed si auto-ripara alla prossima quotazione. ### Sottoscrizioni @@ -269,7 +278,7 @@ Per impostazione predefinita ricevi tutto. Invia un messaggio di controllo per r Omettere un campo corrisponde a qualsiasi valore (`{"symbol":"SOL"}` = SOL su ogni venue). `venue` viene confrontato senza distinzione tra maiuscole e minuscole. -**Risposta di conferma del server:** +**Conferma del server:** ```json {"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} @@ -280,7 +289,7 @@ Gli errori restituiscono `{"channel":"error","error":""}`. ### Heartbeat e liveness - Il server invia un **WebSocket Ping** ogni 20 secondi; i client conformi rispondono automaticamente con Pong. -- I client silenziosi per 60 secondi vengono chiusi e rimossi. +- I client silenti per 60 secondi vengono chiusi e rimossi. - Keepalive a livello applicativo: `{"method":"ping"}` → `{"channel":"pong"}`. ### Scheletro del consumer @@ -304,7 +313,7 @@ def on_message(ws, frame): elif t == "depth": replace_book(msg["venue"], msg["symbol"], msg["bids"], msg["asks"]) - # tipi sconosciuti: ignorare silenziosamente (compatibilità in avanti) + # tipi sconosciuti: ignora silenziosamente (compatibilità in avanti) ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() @@ -312,14 +321,22 @@ ws.run_forever() ### Sorgenti di input e backstop WebSocket -Il feed multicast Edge è sempre attivo. Un **backstop WebSocket pubblico** opzionale può colmare le lacune quando il feed Edge si blocca: +Il feed multicast Edge è sempre attivo. **Backstop WebSocket pubblici** opzionali possono colmare le lacune quando il feed Edge si interrompe. Ne sono disponibili due, ciascuno disattivato per default e abilitabile indipendentemente per venue: + +| Backstop | Abilita con | Copre | URL predefinito | +|----------|-------------|--------|-------------| +| **Hyperliquid** | `WS_INPUT_COINS` (es. `BTC,ETH`) | quotazioni + trade | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (ticker, es. `SOL,BTC`) | **solo trade** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# Abilita il backstop per BTC ed ETH: +# Abilita il backstop Hyperliquid per BTC ed ETH: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Abilita il backstop trade di Phoenix per SOL: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -Le due sorgenti competono per tick `(venue, symbol, source_ts)` all'interno di un arbitro condiviso. In condizioni normali la sorgente Edge vince (sub-ms vs. decine di ms su internet); quando l'Edge presenta lacune, la copia pubblica interviene. L'output WebSocket è identico indipendentemente dalla sorgente che ha consegnato un determinato aggiornamento. +Per ogni tick `(venue, symbol, source_ts)`, le sorgenti Edge e pubbliche competono all'interno di un arbitro condiviso. In condizioni normali la sorgente Edge vince (sub-ms vs. decine di ms su internet); quando l'Edge ha interruzioni, la copia pubblica interviene. L'output WebSocket è identico indipendentemente da quale sorgente ha consegnato un dato aggiornamento. (I backstop di Phoenix coprono solo i trade — Edge rimane l'unica sorgente delle quotazioni Phoenix.) --- @@ -329,30 +346,30 @@ Le due sorgenti competono per tick `(venue, symbol, source_ts)` all'interno di u # Streaming dei log sudo docker logs -f doublezero-edge-connect -# Verifica stato del tunnel +# Verifica dello stato del tunnel sudo docker exec -it doublezero-edge-connect doublezero status -# Verifica latenze del dispositivo +# Verifica delle latenze dei dispositivi sudo docker exec -it doublezero-edge-connect doublezero latency -# Stop e rimozione +# Arresto e rimozione sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-connect ``` !!! note "Nessun TLS" - Il bridge è progettato per una rete trusted/locale. Termina TLS con un reverse proxy se esponi l'endpoint WebSocket esternamente. + Il bridge è pensato per una rete trusted/locale. Termina il TLS con un reverse proxy se esponi l'endpoint WebSocket esternamente. --- ## Monitoraggio (Metriche Prometheus) -L'endpoint delle metriche è **disattivato per impostazione predefinita**. Abilitalo con `METRICS_BIND`: +L'endpoint delle metriche è **disattivato per default**. Abilitalo con `METRICS_BIND`: ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -Poi esegui lo scraping: +Poi esegui lo scrape: ```bash curl -s localhost:9090/metrics | grep '^dz_' @@ -360,17 +377,18 @@ curl -s localhost:9090/metrics | grep '^dz_' Metriche principali: -| Metrica | Cosa monitora | -|---------|---------------| +| Metrica | Cosa traccia | +|--------|---------------| | `dz_feed_up{venue}` | `1` mentre il multicast del venue è attivo, `0` mentre è silenzioso. | | `dz_datagrams_received_total{venue}` | Volume di ingestione per venue. | -| `dz_emit_total{venue,kind}` | Messaggi trasmessi dopo la deduplicazione, per tipo. | +| `dz_emit_total{venue,kind}` | Messaggi trasmessi dopo la dedup, per tipo. | +| `dz_quotes_admitted_total{venue,publisher}` | Quotazioni ammesse dall'arbitro, attribuite alla sorgente vincente. Un aumento di `publisher="public"` significa che un backstop sta colmando un'interruzione Edge (vs. `publisher="edge"` in condizioni normali). | | `dz_quotes_dropped_total{venue}` | Quotazioni obsolete/duplicate soppresse. | | `dz_ws_clients` | Client WebSocket attualmente connessi. | | `dz_ws_messages_sent_total{kind}` | Messaggi inoltrati ai client. | -| `dz_ws_client_lagged_total` | Volte in cui un client lento è stato disconnesso per proteggere il feed. | +| `dz_ws_client_lagged_total` | Numero di volte in cui un client lento è stato disconnesso per proteggere il feed. | -Una sonda di liveness `GET /healthz` è anch'essa servita sullo stesso indirizzo di bind. +Un probe di liveness `GET /healthz` è anch'esso servito sullo stesso indirizzo di bind. --- @@ -379,7 +397,7 @@ Una sonda di liveness `GET /healthz` è anch'essa servita sullo stesso indirizzo Il container è disponibile su GHCR: | Ambiente | Immagine | Tag | -|----------|----------|-----| +|-------------|-------|-----| | Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | | Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | | Devnet (privata) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | @@ -395,7 +413,7 @@ docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta ``` -**Compilazione dal sorgente:** +**Build dal sorgente:** ```bash git clone https://github.com/malbeclabs/doublezero-edge-connect @@ -408,7 +426,7 @@ cargo test --ws-bind 0.0.0.0:8081 ``` -Un buffer di ricezione del kernel più grande è consigliato per feed a raffica: +Un buffer di ricezione del kernel più grande è raccomandato per feed a raffica: ```bash sudo sysctl -w net.core.rmem_max=268435456 @@ -418,14 +436,14 @@ sudo sysctl -w net.core.rmem_max=268435456 ## Limiti e Backpressure -| Limite | Default | Comportamento al superamento | -|--------|---------|------------------------------| -| Client simultanei (`WS_MAX_CLIENTS`) | 64 | La nuova connessione viene rifiutata. | -| Sottoscrizioni per client (`WS_MAX_SUBS`) | 256 | La `subscribe` viene rifiutata con un errore. | +| Limite | Default | Comportamento quando superato | +|-------|---------|------------------------| +| Client concorrenti (`WS_MAX_CLIENTS`) | 64 | La nuova connessione viene rifiutata. | +| Sottoscrizioni per client (`WS_MAX_SUBS`) | 256 | Il `subscribe` viene rifiutato con un errore. | | Messaggi di controllo in ingresso / client / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Il client viene disconnesso. | | Buffer di broadcast (`WS_BROADCAST_CAPACITY`) | 4096 | Un client lento **perde i messaggi più vecchi** (non blocca mai il feed). | -Poiché ogni `quote` e `depth` è stato completo, un consumer che perde messaggi sotto backpressure si auto-ripristina con il messaggio successivo — nessun handshake di risincronizzazione necessario. +Poiché ogni `quote` e `depth` è stato completo, un consumer che perde messaggi sotto backpressure si auto-ripara al messaggio successivo — nessun handshake di risincronizzazione necessario. --- @@ -453,12 +471,12 @@ Poiché ogni `quote` e `depth` è stato completo, un consumer che perde messaggi ### `dz_ws_client_lagged_total` elevato -Il tuo consumer sta leggendo più lentamente di quanto il bridge stia pubblicando. Aumenta il buffer di broadcast con `WS_BROADCAST_CAPACITY`, riduci il tempo di elaborazione per messaggio, oppure aggiungi un thread di lettura dedicato. +Il tuo consumer legge più lentamente di quanto il bridge pubblica. Aumenta il buffer di broadcast con `WS_BROADCAST_CAPACITY`, riduci il tempo di elaborazione per messaggio, oppure aggiungi un thread di lettura dedicato. -### Il container termina immediatamente +### Il container esce immediatamente - Il bridge richiede `--network host` e il device `/dev/net/tun`; un semplice `docker run` senza questi flag fallirà. -- Usa il one-liner dell'installer o l'esatto comando `docker run` mostrato in [Self-hosting](#avanzato-self-hosting). +- Usa la riga singola dell'installer o l'esatto comando `docker run` mostrato in [Self-hosting](#avanzato-self-hosting). ### Il tunnel GRE non si stabilisce diff --git a/docs/Edge Market Data Connection.ja.md b/docs/Edge Market Data Connection.ja.md index ed65f7e..1fccdfb 100644 --- a/docs/Edge Market Data Connection.ja.md +++ b/docs/Edge Market Data Connection.ja.md @@ -1,17 +1,17 @@ --- -description: doublezero-edge-connect を実行して Solana shred をローカル UDP ポートに再転送し、正規化された Edge マーケットデータをローカル WebSocket 経由で受信します。 +description: doublezero-edge-connect を実行して Solana シュレッドをローカル UDP ポートに再転送し、正規化された Edge マーケットデータをローカル WebSocket 経由で受信します。 --- # Edge 接続 -!!! warning "DoubleZero に接続することで、[DoubleZero 利用規約](https://doublezero.xyz/terms-protocol)に同意したものとみなされます。データは内部目的でのみ使用可能であり、再送信は禁止されています(セクション 2(e) を参照)。" +!!! warning "DoubleZero に接続することにより、[DoubleZero 利用規約](https://doublezero.xyz/terms-protocol)に同意したものとみなされます。データは内部利用目的に限り、再送信は禁止されています(セクション 2(e) を参照)。" -`doublezero-edge-connect` は **DoubleZero Edge バイナリマルチキャスト** に参加し、以下の 2 つのフィードとしてローカルに再配信するブリッジです: +`doublezero-edge-connect` は **DoubleZero Edge バイナリマルチキャスト** に参加し、ローカルで以下の 2 つのフィードとして再配信するブリッジです: -1. **Solana shred 転送** — 重複排除済み(オプションで署名検証済み)の shred を 1 つ以上のローカル UDP 宛先にファンアウトし、バリデーターまたは RPC に直接配信します。 -2. **正規化マーケットデータ** — Edge 取引所フィードをデコード・精度補正し、`ws://host:8081` 上の単一 JSON WebSocket として再配信します。 +1. **Solana シュレッド転送** — 重複排除(オプションで署名検証)されたシュレッドを 1 つ以上のローカル UDP 宛先にファンアウトし、バリデーターや RPC に直接供給します。 +2. **正規化マーケットデータ** — Edge のベニューフィードをデコード・精度補正し、単一の JSON WebSocket として `ws://host:8081` で再配信します。 -どちらも同じコンテナと同じワンライナーインストールで動作します。オンチェーン認可で許可されたフィードを有効にしてください。 +どちらも同じコンテナ、同じワンライナーインストールで実行されます。オンチェーン認可で許可されたフィードを有効にしてください。 ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -24,16 +24,16 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## 要件 -- ターゲット環境向けにオンチェーンで認可されたパブリック IPv4 アドレスを持つ **Linux/amd64** ホスト。 -- **Docker**(ワンライナーが未インストールの場合は自動インストールします)。 -- **GRE 接続** — クラウドプロバイダーで IP プロトコル 47 を許可してください。AWS では ENI のソース/宛先チェックを無効にしてください。 -- **DoubleZero アクセスシークレット**: `DZ_` プレフィックス付き base64 トークン、またはキーペアファイルのパス。[DoubleZero オンボーディング](setup.md)プロセスから取得します。 +- 対象環境でオンチェーン認可されたパブリック IPv4 アドレスを持つ **Linux/amd64** ホスト。 +- **Docker**(ワンライナーが未インストールの場合は自動でインストールします)。 +- **GRE 接続** — クラウドプロバイダーで IP プロトコル 47 を許可してください。AWS の場合は ENI のソース/デストチェックを無効にしてください。 +- **DoubleZero アクセスシークレット**:`DZ_` プレフィックス付き base64 トークン、またはキーペアファイルへのパス。[DoubleZero オンボーディング](setup.md)プロセスから取得します。 --- -## ステップ 1: インストールと実行 +## ステップ 1:インストールと実行 -1 つのコマンドでホストを準備し、ブリッジコンテナを起動します。DoubleZero ネットワークに参加し、認可で許可されたすべてのフィード(shred 転送および/または `:8081` のマーケットデータ WebSocket)を開始します: +1 つのコマンドでホストの準備とブリッジコンテナの起動を行います。DoubleZero ネットワークに参加し、認可で許可されたすべてのフィード(シュレッド転送や `:8081` のマーケットデータ WebSocket)を開始します: === "Mainnet-beta" @@ -56,17 +56,17 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ スクリプトが実行する内容: -1. ホストが Linux/amd64 であることを確認し、Docker の存在を確認します(未インストールの場合はインストールを提案)。 -2. GRE トンネル用にホストカーネルを準備します:`tun`/`ip_gre` をロードし、`net.core.rmem_max` を引き上げ、ファイアウォールとクラウドプロバイダーのルールについて警告します。 -3. アクセスシークレットを読み込みます(`DZ_SECRET` が未設定の場合は一度だけプロンプト表示)。 -4. ブリッジコンテナ(`--network host`、`NET_ADMIN`/`NET_RAW`、`/dev/net/tun`)を実行し、`doublezero connect multicast` を実行します。 +1. ホストが Linux/amd64 であることを確認します。 +2. アクセスシークレットを読み込み(`DZ_SECRET` が未設定の場合は一度だけプロンプト表示)、**何かをインストールする前にオンチェーンでアクセスパスを検証します** — レジャーの公開 JSON-RPC に対する純粋なホスト側チェックです。パスがホストと異なる IP にバインドされている場合、(`DZ_CLIENT_IP` で IP が明示的に指定された場合は)中断し、(IP が自動検出のみの場合は NAT 背後で誤検出の可能性があるため)警告して続行します。実際のチェックは `doublezero connect` が行います。 +3. Docker が存在することを確認し(インストールを提案)、GRE トンネル用にホストカーネルを準備します:`tun`/`ip_gre` の読み込み、`net.core.rmem_max` の引き上げ、ファイアウォールやクラウドプロバイダーのルールに関する警告を行います。 +4. ブリッジコンテナを実行し(`--network host`、`NET_ADMIN`/`NET_RAW`、`/dev/net/tun`)、`doublezero connect multicast` を実行します。 -!!! tip "非対話式インストール" - パイプの前に `DZ_SECRET=DZ_…` を設定すると、プロンプトなしで完全に無人実行できます。 +!!! tip "非対話型インストール" + パイプの前に `DZ_SECRET=DZ_…` を設定すると、プロンプトなしで完全に自動実行されます。 --- -## ステップ 2: 設定 +## ステップ 2:設定 すべての設定は**パイプの前に設定する環境変数**で行います。設定ファイルはありません。 @@ -77,42 +77,47 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash ### インストーラー変数 | 変数 | デフォルト | 用途 | -|----------|---------|---------| -| `DZ_SECRET` | *(プロンプト表示)* | `DZ_` プレフィックス付き base64 トークン**または**キーペアファイルのパス。トークンはコンテナに注入されディスクには書き込まれません。ファイルは読み取り専用でバインドマウントされます。 | +|------|-----------|------| +| `DZ_SECRET` | *(プロンプト表示)* | `DZ_` プレフィックス付き base64 トークン **または** キーペアファイルへのパス。トークンはコンテナに注入されディスクには書き込まれません。ファイルは読み取り専用でバインドマウントされます。 | | `DZ_ENV` | スクリプトごと | `mainnet-beta` \| `testnet` \| `devnet`。 | | `DZ_IMAGE` | スクリプトごと | コンテナイメージのオーバーライド。 | | `DZ_NAME` | `doublezero-edge-connect` | コンテナ名。 | -| `DZ_FEEDS` | *(すべて)* | マーケットデータ取り込みを絞り込むカンマ区切りの取引所名(例: `VenueA,VenueB`)。Solana shred 転送には影響しません。 | -| `DZ_ASSUME_YES` | `0` | 確認プロンプトをスキップします(例: Docker インストールプロンプト)。 | -| `DZ_GHCR_TOKEN` | — | **Devnet 限定** — `read:packages` 権限を持つ GHCR トークン(devnet イメージはプライベートです)。 | -| `DZ_GHCR_USER` | `malbeclabs` | **Devnet 限定** — ログイン用の GHCR ユーザー名。 | +| `DZ_FEEDS` | *(すべて)* | マーケットデータ取り込みを絞り込むカンマ区切りのベニュー(例:`VenueA,VenueB`)。Solana シュレッド転送には影響しません。 | +| `DZ_CLIENT_IP` | *(自動検出)* | オンチェーンアクセスパスの事前チェックで使用するパブリック IPv4 のオーバーライド。自動検出が誤っている場合(例:NAT 背後)に設定すると、事前チェックが警告だけでなく確認できるようになります。 | +| `DZ_LEDGER_RPC_URL` | 環境ごと | 事前チェックで使用する DoubleZero レジャー RPC エンドポイントのオーバーライド。 | +| `DZ_ASSUME_YES` | `0` | 確認プロンプトをスキップします(例:Docker インストールプロンプト)。 | +| `DZ_GHCR_TOKEN` | — | **Devnet のみ** — `read:packages` 権限を持つ GHCR トークン(devnet イメージはプライベートです)。 | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet のみ** — ログイン用の GHCR ユーザー名。 | ### ブリッジ変数 -インストーラーは**空でない**ブリッジ変数をすべてコンテナにそのまま転送します。主要なものは以下の通りです: +インストーラーは **空でない** ブリッジ変数をすべてそのままコンテナに転送します。主なものは以下の通りです: | 変数 | デフォルト | 用途 | -|----------|---------|---------| +|------|-----------|------| | `DZ_IFACE` | `doublezero1` | リッスンするネットワークインターフェース。 | -| `DZ_RECV_BUF` | — | UDP 受信バッファのオーバーライド(バイト)。 | -| `METRICS_BIND` | *(空 / 無効)* | Prometheus `/metrics` エンドポイントを有効にします(例: `127.0.0.1:9090`)。 | -| `RUST_LOG` | `info` | ログレベル(`debug`、`warn` など)。 | -| `DZ_SHRED_FORWARD` | — | 転送 shred のローカル UDP 宛先 — [Solana Shred 転送](#solana-shred-転送)を参照。 | -| `WS_BIND` | `0.0.0.0:8081` | マーケットデータ WebSocket のバインドアドレス — [マーケットデータ WebSocket](#マーケットデータ-websocket) を参照。 | -| `WS_MAX_CLIENTS` | `64` | WebSocket の最大同時接続クライアント数。 | -| `WS_INPUT_COINS` | *(空 / 無効)* | 指定シンボルのパブリック WebSocket バックストップを有効にします(例: `BTC,ETH`)。 | +| `DZ_RECV_BUF` | `8388608` | UDP 受信バッファのオーバーライド(バイト単位、デフォルト 8 MiB)。 | +| `METRICS_BIND` | *(空 / 無効)* | Prometheus `/metrics` エンドポイントの有効化(例:`127.0.0.1:9090`)。 | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | ログレベル(`debug`、`warn` など)。 | +| `DZ_SHRED_FORWARD` | — | 転送シュレッドのローカル UDP 宛先 — [Solana シュレッド転送](#solana-シュレッド転送)を参照。 | +| `WS_BIND` | `0.0.0.0:8081` | マーケットデータ WebSocket のバインドアドレス — [マーケットデータ WebSocket](#マーケットデータ-websocket)を参照。 | +| `WS_MAX_CLIENTS` | `64` | 同時接続 WebSocket クライアントの最大数。 | +| `WS_INPUT_COINS` | *(空 / 無効)* | 上場シンボルに対する Hyperliquid パブリック WebSocket バックストップの有効化(例:`BTC,ETH`) — [入力ソース](#入力ソースと-websocket-バックストップ)を参照。 | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | バックストップ用の Hyperliquid パブリック WebSocket URL。 | +| `PHOENIX_WS_INPUT_MARKETS` | *(空 / 無効)* | 上場ティッカーに対する Phoenix パブリック WebSocket バックストップ(トレードのみ)の有効化(例:`SOL,BTC`)。 | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | バックストップ用の Phoenix パブリック WebSocket URL。 | **例:** ```bash -# ローカルのバリデーター/RPC に shred を転送: +# ローカルバリデーター/RPC にシュレッドを転送: DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# 非対話式、testnet: +# 非対話型、テストネット: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# 特定の取引所に絞り込み、詳細ログ、デフォルト以外の WS ポート: +# 特定のベニューに絞り込み、詳細ログ、デフォルト以外の WS ポート: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash @@ -122,13 +127,13 @@ DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ ``` !!! note - インストーラーは**空でない**値のみを転送するため、ワンライナーで空のオーバーライド(例: WebSocket シンクを無効にする `WS_BIND=""`)を渡すことはできません。その場合は手動の `docker run` を使用してください — [セルフホスティング](#上級-セルフホスティング)を参照。 + インストーラーは**空でない**値のみを転送しますが、1 つだけ例外があります:`WS_BIND` は空に設定した場合でも転送されるため、`WS_BIND=""` でワンライナーを通じて WebSocket シンクを**無効化できます**。その他の変数については、空のオーバーライドはパイプを通じて渡せません — その場合は手動で `docker run` を記述してください([セルフホスティング](#上級-セルフホスティング)を参照)。 --- -## Solana Shred 転送 +## Solana シュレッド転送 -ブリッジは `edge-solana-*` shred マルチキャストグループに参加し、各データグラムを 1 つ以上のローカル UDP 宛先にファンアウトします。Edge ネットワークからバリデーターまたは RPC に直接フィードします。認可にこれらのグループが含まれている場合、検出時に自動的にアクティブになります。 +ブリッジは `edge-solana-*` シュレッドマルチキャストグループに参加し、各データグラムを 1 つ以上のローカル UDP 宛先にファンアウトします — Edge ネットワークから直接バリデーターや RPC にフィードします。認可にこれらのグループが含まれている場合、検出時に自動的にアクティベートされます。 ```bash # デフォルト(重複排除のみ、ローカルポート 20000 に転送): @@ -142,29 +147,33 @@ DZ_SHRED_DEDUP_MODE=sigverify \ ``` | 変数 | デフォルト | 用途 | -|----------|---------|---------| -| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 転送 shred の宛先(繰り返し指定可能)。 | -| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(shred ごとに 1 コピー)、`sigverify`(+ ed25519 検証)、`none`(全データグラム)。 | -| `DZ_SHRED_RPC_URL` | — | Solana RPC エンドポイント。`sigverify` モードで必須。 | +|------|-----------|------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 転送シュレッドの宛先(繰り返し指定可能)。 | +| `DZ_SHRED_DISABLE` | `0` | マスターオプトアウト(`--shred-forward-disable`)。認可で許可されていても転送を無効にします — ローカルコンシューマーがリッスンしていない場合に設定して、シュレッドファイアホースを宛先なしに転送する CPU 消費を防ぎます。 | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(シュレッドごとに 1 コピー)、`sigverify`(+ ed25519 検証)、`none`(全データグラム)。 | +| `DZ_SHRED_RPC_URL` | — | Solana RPC エンドポイント。`sigverify` モードで必要です。 | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 重複排除ウィンドウのサイズ。 | -完全なパイプラインと注意事項については [Shred forwarding](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) を参照してください。 +完全なパイプラインと注意事項については[シュレッド転送](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)を参照してください。 --- ## マーケットデータ WebSocket -`ws://:8081` に WebSocket 接続を開き、JSON フレームを読み取ります。認可された取引所のデータをすべて受信します。オプションの `subscribe` メッセージでストリームを特定の取引所やシンボルに絞り込めます。 +`ws://:8081` に WebSocket 接続を開き、JSON フレームを読み取ります。認可されているすべてのベニューを受信します。オプションの `subscribe` メッセージでストリームを特定のベニューやシンボルに絞り込めます。 -WebSocket + JSON に対応する任意のエンジンが、薄い(〜50–100 行の)アダプターで消費できます。バイナリマルチキャスト、取引所ごとの 2 ポート分割、マニフェスト/精度ハンドシェイクはすべてブリッジ内に留まります。コンシューマーがコーディングする唯一の契約は WebSocket JSON です。 +WebSocket + JSON に対応するエンジンであれば、薄い(約 50〜100 行の)アダプターで利用できます。バイナリマルチキャスト、ベニューごとの 2 ポート分割、マニフェスト/精度ハンドシェイクはすべてブリッジ内部に閉じており、コンシューマーがコーディングする唯一のコントラクトは WebSocket JSON です。 + +!!! note + WebSocket シンクは、認可に対して少なくとも 1 つのマーケットデータフィードがアクティブな場合にのみ起動します — シュレッドのみのホストでは WebSocket は提供されません。アクティベーションはオンチェーンサブスクリプションリコンサイラーによって駆動され、30 秒ごとに更新されます(`--subscription-refresh-secs`)。`--subscription-gating-disable` でゲーティングをオプトアウトできます。 ### 接続ライフサイクル -新しい接続ごとにブリッジは以下を行います: +新しい接続ごとにブリッジは以下を実行します: -1. **現在のインストゥルメント定義をリプレイ** — 既知のシンボルごとに 1 つの `instrument` メッセージ — コンシューマーが最初の気配値の前に精度情報を得られるようにします。 -2. **シンボルごとの最新板情報スナップショットをリプレイ**(Market-by-Order フィードがアクティブな場合)。 -3. `quote` / `trade` / `midpoint` / `depth` メッセージが到着次第、接続済みのすべてのコンシューマーに**ストリーミング**します。 +1. **現在のインストゥルメント定義をリプレイ** — 既知のシンボルごとに 1 つの `instrument` メッセージ — コンシューマーが最初のクオートの前に精度情報を取得できるようにします。 +2. **シンボルごとの最新デプススナップショットをリプレイ**(Market-by-Order フィードがアクティブな場合)。 +3. `quote` / `trade` / `midpoint` / `depth` メッセージが到着次第**ストリーミング**し、接続されたすべてのコンシューマーにファンアウトします。 ``` connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … @@ -175,13 +184,13 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade すべてのメッセージは `type` フィールドでタグ付けされた JSON オブジェクトです: | `type` | 意味 | -|--------|---------| -| `instrument` | インストゥルメント/精度定義。 | -| `quote` | 最良気配値の更新(フルステート)。 | -| `trade` | 約定プリント(直近の取引)。 | -| `midpoint` | 算出されたミッド価格。 | -| `depth` | フルオーダーブック板情報スナップショット。 | -| `status` | 取引所レベルのフィードヘルス遷移。 | +|--------|------| +| `instrument` | インストゥルメント/精度の定義。 | +| `quote` | ベストビッド・オファーの更新(完全な状態)。 | +| `trade` | 約定情報(直近の取引)。 | +| `midpoint` | 派生ミッド価格。 | +| `depth` | フルオーダーブックのデプススナップショット。 | +| `status` | ベニューレベルのフィードヘルス遷移。 | コンシューマーは**未知の `type` 値および未知のフィールドを無視しなければなりません**(前方互換性)。 @@ -191,7 +200,7 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -接続時および定義変更時に送信されます。`price_exponent` と `qty_exponent` は取引所のティックサイズとサイズステップを 10 のべき乗で表します。 +接続時および定義が変更されたときに送信されます。`price_exponent` と `qty_exponent` はベニューのティックサイズとサイズステップを 10 のべき乗で表します。 #### `quote` @@ -210,14 +219,14 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade } ``` -すべての `quote` は**フルステート**です — メッセージが欠落しても次の quote で自動復旧し、再同期は不要です。4 つのタイムスタンプでエンドツーエンドのレイテンシを分解できます: +すべての `quote` は**完全な状態**です — メッセージをドロップしても次のクオートで自己回復するため、再同期は不要です。4 つのタイムスタンプでエンドツーエンドのレイテンシを分解できます: ``` source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) venue book wire arrival post-decode WS hand-off ``` -`0` は「利用不可」のセンチネル値です — 1970 年ではなく欠損として扱ってください。 +`0` は「利用不可」を示すセンチネル値です — 1970 年ではなく、欠損として扱ってください。 #### `trade` @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`aggressor_side` は `"buy"`、`"sell"`、または `"unknown"` です。約定はポイントインタイムのイベントであり、再接続時にはリプレイされません。 +`aggressor_side` は `"buy"`、`"sell"`、または `"unknown"` です。トレードはポイントインタイムのイベントであり、再接続時にリプレイされません。 #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`bids` は価格の高い順にソート、`asks` は価格の低い順にソートされています。各 `depth` は**フルスナップショット**です — マージではなく置換してください。 +`bids` は価格の高い順、`asks` は価格の低い順にソートされています。各 `depth` は**完全なスナップショット**です — マージではなく置換してください。 #### `status` @@ -256,34 +265,34 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -取引所の quote マルチキャストが沈黙(`state:"down"`)または回復(`state:"ok"`)した際に Edge 上で発行されます。UI で取引所をグレーアウト表示するのに使用してください。Quote の配信は status に依存しません — フィードは次の quote で自動復旧します。 +ベニューのクオートマルチキャストが無音になった場合(`state:"down"`)または回復した場合(`state:"ok"`)にエッジで発行されます。UI でベニューをグレーアウトするために使用してください。クオート配信はステータスでゲートされません — フィードは次のクオートで自己回復します。 ### サブスクリプション -デフォルトではすべてを受信します。ストリームを絞り込むにはコントロールメッセージを送信します: +デフォルトではすべてを受信します。ストリームを絞り込むにはコントロールメッセージを送信してください: ```json {"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} {"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -フィールドを省略すると任意の値にマッチします(`{"symbol":"SOL"}` = すべての取引所の SOL)。`venue` は大文字小文字を区別しません。 +フィールドを省略すると任意の値にマッチします(`{"symbol":"SOL"}` = すべてのベニューの SOL)。`venue` は大文字小文字を区別せずにマッチングされます。 -**サーバー応答:** +**サーバー確認応答:** ```json {"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -エラーは `{"channel":"error","error":""}` を返します。 +エラーの場合は `{"channel":"error","error":""}` が返されます。 -### ハートビートと生存確認 +### ハートビートとライブネス -- サーバーは 20 秒ごとに **WebSocket Ping** を送信します。準拠クライアントは自動で Pong を返します。 -- 60 秒間無応答のクライアントは切断・解放されます。 +- サーバーは 20 秒ごとに **WebSocket Ping** を送信します。準拠クライアントは自動的に Pong を返します。 +- 60 秒間無通信のクライアントは切断され、回収されます。 - アプリケーションレベルのキープアライブ:`{"method":"ping"}` → `{"channel":"pong"}`。 -### コンシューマーのスケルトン +### コンシューマースケルトン ```python import json, websocket @@ -312,14 +321,22 @@ ws.run_forever() ### 入力ソースと WebSocket バックストップ -Edge マルチキャストフィードは常時オンです。オプションの**パブリック WebSocket バックストップ**は、Edge フィードが停止した際にギャップを補います: +Edge マルチキャストフィードは常時稼働です。オプションの**パブリック WebSocket バックストップ**で、Edge フィードが停止した際のギャップを埋めることができます。2 つのバックストップが利用可能で、それぞれデフォルトでは無効、ベニューごとに独立して有効化されます: + +| バックストップ | 有効化方法 | カバー範囲 | デフォルト URL | +|---------------|-----------|-----------|---------------| +| **Hyperliquid** | `WS_INPUT_COINS`(例:`BTC,ETH`) | クオート + トレード | `wss://api.hyperliquid.xyz/ws`(`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS`(ティッカー、例:`SOL,BTC`) | **トレードのみ** | `wss://perp-api.phoenix.trade/v1/ws`(`PHOENIX_WS_INPUT_URL`) | ```bash -# BTC と ETH のバックストップを有効化: +# Hyperliquid バックストップを BTC と ETH で有効化: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Phoenix トレードバックストップを SOL で有効化: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -2 つのソースは共有アービターの中で `(venue, symbol, source_ts)` ティックごとに競争します。定常状態では Edge ソースが勝利します(インターネット経由の数十 ms に対してサブ ms)。Edge にギャップが生じた場合、パブリックコピーが補完します。どのソースが更新を配信したかに関わらず、WebSocket 出力は同一です。 +各 `(venue, symbol, source_ts)` ティックに対して、Edge とパブリックソースが共有アービターの中で競合します。定常状態では Edge ソースが勝ちます(サブミリ秒 vs. インターネット経由の数十ミリ秒)。Edge にギャップが生じた場合、パブリックコピーが補完します。WebSocket の出力は、どのソースが更新を配信したかに関係なく同一です。(Phoenix バックストップはトレードのみをカバーします — Phoenix のクオートについては Edge が唯一のソースのままです。) --- @@ -329,7 +346,7 @@ WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/co # ログをストリーミング sudo docker logs -f doublezero-edge-connect -# トンネル状態を確認 +# トンネルの状態を確認 sudo docker exec -it doublezero-edge-connect doublezero status # デバイスレイテンシを確認 @@ -340,19 +357,19 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne ``` !!! note "TLS なし" - ブリッジは信頼済み/ローカルネットワークを対象としています。WebSocket エンドポイントを外部に公開する場合は、リバースプロキシで TLS を終端してください。 + ブリッジは信頼された / ローカルネットワークを対象としています。WebSocket エンドポイントを外部に公開する場合は、リバースプロキシで TLS を終端してください。 --- -## モニタリング(Prometheus メトリクス) +## 監視(Prometheus メトリクス) -メトリクスエンドポイントは**デフォルトで無効**です。`METRICS_BIND` で有効にします: +メトリクスエンドポイントは**デフォルトで無効**です。`METRICS_BIND` で有効化してください: ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -スクレイプ: +スクレイピング: ```bash curl -s localhost:9090/metrics | grep '^dz_' @@ -361,25 +378,26 @@ curl -s localhost:9090/metrics | grep '^dz_' 主要メトリクス: | メトリクス | 追跡内容 | -|--------|---------------| -| `dz_feed_up{venue}` | 取引所のマルチキャストがライブの間 `1`、沈黙中は `0`。 | -| `dz_datagrams_received_total{venue}` | 取引所ごとの取り込み量。 | +|-----------|---------| +| `dz_feed_up{venue}` | そのベニューのマルチキャストがライブの間は `1`、無音の間は `0`。 | +| `dz_datagrams_received_total{venue}` | ベニューごとの取り込みボリューム。 | | `dz_emit_total{venue,kind}` | 重複排除後にブロードキャストされたメッセージ(タイプ別)。 | -| `dz_quotes_dropped_total{venue}` | 抑制された古い/重複 quote。 | +| `dz_quotes_admitted_total{venue,publisher}` | アービターによって採用されたクオート(勝利ソース別に帰属)。`publisher="public"` の上昇は、バックストップが Edge のギャップを埋めていることを意味します(定常状態では `publisher="edge"`)。 | +| `dz_quotes_dropped_total{venue}` | 抑制された古い/重複クオート。 | | `dz_ws_clients` | 現在接続中の WebSocket クライアント数。 | | `dz_ws_messages_sent_total{kind}` | クライアントに転送されたメッセージ。 | -| `dz_ws_client_lagged_total` | フィードを保護するために遅延クライアントが切断された回数。 | +| `dz_ws_client_lagged_total` | フィードを保護するために低速クライアントが切断された回数。 | -`GET /healthz` 生存確認プローブも同じバインドアドレスで提供されます。 +同じバインドアドレスで `GET /healthz` ライブネスプローブも提供されます。 --- -## 上級: セルフホスティング +## 上級:セルフホスティング コンテナは GHCR で利用可能です: | 環境 | イメージ | タグ | -|-------------|-------|-----| +|------|---------|-----| | Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | | Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | | Devnet (プライベート) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | @@ -387,77 +405,4 @@ curl -s localhost:9090/metrics | grep '^dz_' 手動で実行します(インストーラーが転送できないオプション、例えば `WS_BIND=""` が必要な場合): ```bash -docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ - -e DZ_SECRET=DZ_… \ - -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ - -e WS_BIND=0.0.0.0:8081 \ - -e METRICS_BIND=127.0.0.1:9090 \ - ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta -``` - -**ソースからビルド:** - -```bash -git clone https://github.com/malbeclabs/doublezero-edge-connect -cd doublezero-edge-connect -cargo build --release -cargo test - -./target/release/doublezero-edge-connect \ - --iface doublezero1 \ - --ws-bind 0.0.0.0:8081 -``` - -バースト性の高いフィードには、より大きなカーネル受信バッファを推奨します: - -```bash -sudo sysctl -w net.core.rmem_max=268435456 -``` - ---- - -## 制限とバックプレッシャー - -| 制限 | デフォルト | 超過時の動作 | -|-------|---------|------------------------| -| 同時接続クライアント数 (`WS_MAX_CLIENTS`) | 64 | 新しい接続が拒否されます。 | -| クライアントごとのサブスクリプション数 (`WS_MAX_SUBS`) | 256 | `subscribe` がエラーで拒否されます。 | -| クライアントごとの受信制御メッセージ数/分 (`WS_MAX_INBOUND_PER_MIN`) | 600 | クライアントが切断されます。 | -| ブロードキャストバッファ (`WS_BROADCAST_CAPACITY`) | 4096 | 遅延クライアントは**最も古いメッセージをドロップ**します(フィードを停止させることはありません)。 | - -すべての `quote` と `depth` はフルステートであるため、バックプレッシャーでメッセージをドロップしたコンシューマーは次のメッセージで自動復旧します — 再同期ハンドシェイクは不要です。 - ---- - -## トラブルシューティング - -### ローカルポートに shred が到着しない - -- オンチェーンで `edge-solana-*` shred グループへのアクセスが認可されていることを確認してください。 -- トンネルが稼働中か確認してください:`sudo docker exec -it doublezero-edge-connect doublezero status` -- 参加エラーのログを確認してください:`sudo docker logs -f doublezero-edge-connect` -- `DZ_SHRED_FORWARD` が到達可能なローカル UDP 宛先を指していることを確認してください。 - -### 取引所からメッセージが来ない - -- トンネルが稼働中か確認してください:`sudo docker exec -it doublezero-edge-connect doublezero status` -- 参加エラーのログを確認してください:`sudo docker logs -f doublezero-edge-connect` -- オンチェーンでその取引所へのアクセスが認可されていることを確認してください。 -- 問題を切り分けるため、`DZ_FEEDS=` でその取引所に取り込みを絞り込んでください。 - -### WebSocket は接続するが quote が到着しない - -- `instrument` メッセージが常に最初に到着します。リファレンスデータのハンドシェイクが完了すると quote が続きます。データが欠落していると判断する前に、接続後 10〜20 秒待ってください。 -- メトリクスの `dz_feed_up{venue}` を確認してください — `0` はホスト上でマルチキャストが沈黙していることを意味します。 -- ファイアウォールルールが `doublezero1` インターフェース上のマルチキャスト UDP を許可していることを確認してください。 - -### `dz_ws_client_lagged_total` が高い - -コンシューマーの読み取り速度がブリッジの配信速度より遅くなっています。`WS_BROADCAST_CAPACITY` でブロードキャストバッファを増やすか、メッセージごとの処理時間を短縮するか、専用のリーダースレッドを追加してください。 - -### コンテナが即座に終了する - -- ブリッジには `--network host` と `/dev/net/tun` デバイスが必要です。これらのフラグなしの通常の `docker run` は失敗します。 -- インストーラーのワンライナーまたは[セルフホスティング](#上級-セルフホスティング)に記載されている正確な `docker run` コマンドを使用してください。 - -### GRE トンネルが確立されない \ No newline at end of file +docker run --rm --network host --cap-add NET_ADMIN -- \ No newline at end of file diff --git a/docs/Edge Market Data Connection.ko.md b/docs/Edge Market Data Connection.ko.md index 9c69325..e08bbc2 100644 --- a/docs/Edge Market Data Connection.ko.md +++ b/docs/Edge Market Data Connection.ko.md @@ -1,17 +1,17 @@ --- -description: doublezero-edge-connect를 실행하여 Solana 시레드(shred)를 로컬 UDP 포트로 재전달하고 정규화된 Edge 시장 데이터를 로컬 WebSocket을 통해 소비합니다. +description: doublezero-edge-connect를 실행하여 Solana shred를 로컬 UDP 포트로 재전달하고 로컬 WebSocket을 통해 정규화된 Edge 시장 데이터를 소비합니다. --- # Edge 연결 -!!! warning "DoubleZero에 연결함으로써 [DoubleZero 이용약관](https://doublezero.xyz/terms-protocol)에 동의합니다. 데이터는 내부 목적으로만 사용할 수 있으며 재전송할 수 없습니다(제2조(e) 참조)." +!!! warning "DoubleZero에 연결함으로써 [DoubleZero 이용약관](https://doublezero.xyz/terms-protocol)에 동의합니다. 데이터는 내부 목적으로만 사용 가능하며 재전송할 수 없습니다(Section 2(e) 참조)." -`doublezero-edge-connect`는 **DoubleZero Edge 바이너리 멀티캐스트**에 참여하여 이를 로컬에서 두 가지 피드로 제공하는 브리지입니다: +`doublezero-edge-connect`는 **DoubleZero Edge 바이너리 멀티캐스트**에 참여하여 로컬에서 두 가지 피드로 제공하는 브리지입니다: -1. **Solana 시레드 포워딩** — 중복 제거된(선택적으로 서명 검증된) 시레드를 하나 이상의 로컬 UDP 대상으로 팬아웃하여 밸리데이터 또는 RPC에서 바로 사용할 수 있습니다. -2. **정규화된 시장 데이터** — Edge 거래소 피드를 디코딩하고 정밀도를 보정한 후 `ws://host:8081`에서 단일 JSON WebSocket으로 제공합니다. +1. **Solana shred 전달** — 중복 제거된(선택적으로 서명 검증된) shred를 하나 이상의 로컬 UDP 대상으로 팬아웃하여 밸리데이터 또는 RPC에 바로 사용할 수 있습니다. +2. **정규화된 시장 데이터** — Edge 거래소 피드를 디코딩하고, 정밀도를 보정하여, `ws://host:8081`에서 단일 JSON WebSocket으로 재제공합니다. -두 피드 모두 동일한 컨테이너와 동일한 원라인 설치로 실행됩니다. 온체인 인가가 부여한 피드를 활성화하세요. +두 피드 모두 동일한 컨테이너와 동일한 한 줄 설치에서 실행됩니다. 온체인 인가에서 허용하는 피드를 활성화하세요. ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -24,16 +24,16 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## 요구 사항 -- 대상 환경에 대해 온체인으로 인가된 공인 IPv4 주소를 가진 **Linux/amd64** 호스트. -- **Docker** (원라이너가 없는 경우 설치합니다). -- **GRE 연결** — 클라우드 제공자에서 IP 프로토콜 47을 허용하세요. AWS에서는 ENI 소스/목적지 확인을 비활성화하세요. +- 대상 환경에 대해 온체인으로 인가된 퍼블릭 IPv4 주소를 가진 **Linux/amd64** 호스트. +- **Docker** (없는 경우 원라이너가 설치합니다). +- **GRE 연결** — 클라우드 제공업체에서 IP 프로토콜 47을 허용하세요; AWS에서는 ENI source/dest 체크를 비활성화하세요. - **DoubleZero 액세스 시크릿**: `DZ_` 접두사가 붙은 base64 토큰 또는 키페어 파일 경로로, [DoubleZero 온보딩](setup.md) 과정에서 발급받습니다. --- ## 1단계: 설치 및 실행 -하나의 명령으로 호스트를 준비하고 브리지 컨테이너를 시작합니다. DoubleZero 네트워크에 참여하고 인가가 부여한 모든 피드(시레드 포워딩 및/또는 `:8081`의 시장 데이터 WebSocket)를 시작합니다: +하나의 명령으로 호스트를 준비하고 브리지 컨테이너를 시작합니다. DoubleZero 네트워크에 참여하고 인가에서 허용하는 모든 피드를 시작합니다 — shred 전달 및/또는 `:8081`의 시장 데이터 WebSocket: === "Mainnet-beta" @@ -50,19 +50,19 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ === "Devnet (비공개)" ```bash - # read:packages 권한이 있는 GHCR 토큰이 필요합니다 + # read:packages 권한이 있는 GHCR 토큰 필요 DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash ``` 스크립트가 수행하는 작업: -1. 호스트가 Linux/amd64인지 확인하고, Docker가 있는지 확인합니다(없으면 설치를 제안합니다). -2. GRE 터널을 위해 호스트 커널을 준비합니다: `tun`/`ip_gre` 로드, `net.core.rmem_max` 증가, 방화벽 및 클라우드 제공자 규칙에 대한 경고. -3. 액세스 시크릿을 로드합니다(`DZ_SECRET`이 설정되지 않은 경우 한 번 프롬프트됩니다). +1. 호스트가 Linux/amd64인지 확인합니다. +2. 액세스 시크릿을 로드하고(`DZ_SECRET`이 설정되지 않은 경우 한 번 입력 요청) **설치 전에 온체인에서 액세스 패스를 검증합니다** — 레저의 퍼블릭 JSON-RPC를 대상으로 하는 순수 호스트 측 검사입니다. 패스가 호스트의 IP와 다른 IP에 바인딩된 경우, (`DZ_CLIENT_IP`를 통해 명시적으로 IP를 지정한 경우) 중단하거나, (NAT 뒤에서 잘못될 수 있는 자동 감지된 IP인 경우) 경고 후 계속 진행하며, `doublezero connect`가 실제 검사를 수행합니다. +3. Docker가 있는지 확인하고(없으면 설치를 제안) GRE 터널을 위한 호스트 커널을 준비합니다: `tun`/`ip_gre` 로드, `net.core.rmem_max` 증가, 방화벽 및 클라우드 제공업체 규칙에 대한 경고. 4. 브리지 컨테이너를 실행하고(`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) `doublezero connect multicast`를 실행합니다. -!!! tip "비대화형 설치" - 파이프 앞에 `DZ_SECRET=DZ_…`를 설정하면 프롬프트 없이 완전히 무인으로 실행됩니다. +!!! tip "비대화식 설치" + 파이프 앞에 `DZ_SECRET=DZ_…`를 설정하면 프롬프트 없이 완전 자동으로 실행됩니다. --- @@ -74,45 +74,50 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash ``` -### 설치 프로그램 변수 +### 인스톨러 변수 | 변수 | 기본값 | 용도 | -|----------|---------|---------| -| `DZ_SECRET` | *(프롬프트)* | `DZ_` 접두사가 붙은 base64 토큰 **또는** 키페어 파일 경로. 토큰은 컨테이너에 주입되며 디스크에 기록되지 않습니다. 파일은 읽기 전용으로 바인드 마운트됩니다. | +|------|--------|------| +| `DZ_SECRET` | *(입력 요청)* | `DZ_` 접두사가 붙은 base64 토큰 **또는** 키페어 파일 경로. 토큰은 컨테이너에 주입되며 디스크에 기록되지 않습니다; 파일은 읽기 전용으로 바인드 마운트됩니다. | | `DZ_ENV` | 스크립트별 | `mainnet-beta` \| `testnet` \| `devnet`. | -| `DZ_IMAGE` | 스크립트별 | 컨테이너 이미지를 재정의합니다. | +| `DZ_IMAGE` | 스크립트별 | 컨테이너 이미지를 오버라이드합니다. | | `DZ_NAME` | `doublezero-edge-connect` | 컨테이너 이름. | -| `DZ_FEEDS` | *(전체)* | 시장 데이터 수집을 좁히는 쉼표로 구분된 거래소 목록 (예: `VenueA,VenueB`). Solana 시레드 포워딩에는 영향을 미치지 않습니다. | -| `DZ_ASSUME_YES` | `0` | 확인 프롬프트를 건너뜁니다 (예: Docker 설치 프롬프트). | -| `DZ_GHCR_TOKEN` | — | **Devnet 전용** — `read:packages` 권한이 있는 GHCR 토큰(devnet 이미지는 비공개). | -| `DZ_GHCR_USER` | `malbeclabs` | **Devnet 전용** — 로그인을 위한 GHCR 사용자명. | +| `DZ_FEEDS` | *(전체)* | 시장 데이터 수집을 특정 거래소로 좁히기 위한 쉼표 구분 목록 (예: `VenueA,VenueB`). Solana shred 전달에는 영향을 주지 않습니다. | +| `DZ_CLIENT_IP` | *(자동 감지)* | 온체인 액세스 패스 사전 검사에 사용되는 퍼블릭 IPv4를 오버라이드합니다. 자동 감지가 잘못된 경우(예: NAT 뒤) 설정하면 사전 검사가 경고만 하는 대신 확인할 수 있습니다. | +| `DZ_LEDGER_RPC_URL` | 환경별 | 사전 검사에 사용되는 DoubleZero 레저 RPC 엔드포인트를 오버라이드합니다. | +| `DZ_ASSUME_YES` | `0` | 확인 프롬프트를 건너뜁니다(예: Docker 설치 프롬프트). | +| `DZ_GHCR_TOKEN` | — | **Devnet 전용** — `read:packages` 권한이 있는 GHCR 토큰 (devnet 이미지는 비공개). | +| `DZ_GHCR_USER` | `malbeclabs` | **Devnet 전용** — 로그인용 GHCR 사용자 이름. | ### 브리지 변수 -설치 프로그램은 **비어 있지 않은** 모든 브리지 변수를 컨테이너로 직접 전달합니다. 주요 변수: +인스톨러는 **비어 있지 않은** 모든 브리지 변수를 컨테이너로 직접 전달합니다. 주요 항목: | 변수 | 기본값 | 용도 | -|----------|---------|---------| +|------|--------|------| | `DZ_IFACE` | `doublezero1` | 리스닝할 네트워크 인터페이스. | -| `DZ_RECV_BUF` | — | UDP 수신 버퍼 재정의 (바이트). | +| `DZ_RECV_BUF` | `8388608` | UDP 수신 버퍼 오버라이드 (바이트; 기본 8 MiB). | | `METRICS_BIND` | *(비어 있음 / 비활성)* | Prometheus `/metrics` 엔드포인트 활성화 (예: `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | 로그 레벨 (`debug`, `warn` 등). | -| `DZ_SHRED_FORWARD` | — | 포워딩된 시레드의 로컬 UDP 대상 — [Solana 시레드 포워딩](#solana-시레드-포워딩) 참조. | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | 로그 수준 (`debug`, `warn` 등). | +| `DZ_SHRED_FORWARD` | — | 전달된 shred를 위한 로컬 UDP 대상 — [Solana Shred 전달](#solana-shred-전달) 참조. | | `WS_BIND` | `0.0.0.0:8081` | 시장 데이터 WebSocket 바인드 주소 — [시장 데이터 WebSocket](#시장-데이터-websocket) 참조. | | `WS_MAX_CLIENTS` | `64` | 최대 동시 WebSocket 클라이언트 수. | -| `WS_INPUT_COINS` | *(비어 있음 / 비활성)* | 나열된 심볼에 대한 공개 WebSocket 백스톱 활성화 (예: `BTC,ETH`). | +| `WS_INPUT_COINS` | *(비어 있음 / 비활성)* | 나열된 심볼에 대한 Hyperliquid 공개 WebSocket 백스톱 활성화 (예: `BTC,ETH`) — [입력 소스](#입력-소스-및-websocket-백스톱) 참조. | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | 백스톱용 Hyperliquid 공개 WebSocket URL. | +| `PHOENIX_WS_INPUT_MARKETS` | *(비어 있음 / 비활성)* | 나열된 티커에 대한 Phoenix 공개 WebSocket 백스톱(거래만) 활성화 (예: `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | 백스톱용 Phoenix 공개 WebSocket URL. | **예시:** ```bash -# 로컬 밸리데이터/RPC로 시레드 포워딩: +# 로컬 밸리데이터/RPC로 shred 전달: DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# 비대화형, testnet: +# 비대화식, testnet: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# 특정 거래소로 시장 데이터 좁히기, 상세 로깅, 비기본 WS 포트: +# 특정 거래소로 시장 데이터 좁히기, 상세 로깅, 기본이 아닌 WS 포트: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash @@ -122,16 +127,16 @@ DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ ``` !!! note - 설치 프로그램은 **비어 있지 않은** 값만 전달하므로, 원라이너를 통해 빈 재정의(예: WebSocket 싱크를 비활성화하기 위한 `WS_BIND=""`)를 전달할 수 없습니다. 이 경우 수동으로 `docker run`을 작성하세요 — [자체 호스팅](#고급-자체-호스팅) 참조. + 인스톨러는 **비어 있지 않은** 값만 전달하며, 한 가지 예외가 있습니다: `WS_BIND`는 비어 있게 설정해도 전달되므로, `WS_BIND=""`는 원라이너를 통해 WebSocket 싱크를 **비활성화합니다**. 다른 변수의 경우 빈 오버라이드는 파이프를 통해 전달할 수 없습니다 — 이 경우 직접 작성한 `docker run`을 사용하세요([셀프 호스팅](#고급-셀프-호스팅) 참조). --- -## Solana 시레드 포워딩 +## Solana Shred 전달 -브리지는 `edge-solana-*` 시레드 멀티캐스트 그룹에 참여하고 각 데이터그램을 하나 이상의 로컬 UDP 대상으로 팬아웃합니다 — Edge 네트워크에서 직접 밸리데이터 또는 RPC에 공급합니다. 인가에 해당 그룹이 포함되어 있으면 검색 시 자동으로 활성화됩니다. +브리지는 `edge-solana-*` shred 멀티캐스트 그룹에 참여하고 각 데이터그램을 하나 이상의 로컬 UDP 대상으로 팬아웃합니다 — Edge 네트워크에서 직접 밸리데이터 또는 RPC에 공급합니다. 해당 그룹이 인가에 포함되어 있으면 검색 시 자동으로 활성화됩니다. ```bash -# 기본값 (중복 제거만, 로컬 포트 20000으로 포워딩): +# 기본값 (중복 제거만, 로컬 포트 20000으로 전달): DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash # 서명 검증 포함: @@ -142,32 +147,36 @@ DZ_SHRED_DEDUP_MODE=sigverify \ ``` | 변수 | 기본값 | 용도 | -|----------|---------|---------| -| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 포워딩된 시레드의 대상 (반복 가능). | -| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (시레드당 한 복사본), `sigverify` (+ ed25519 검증), `none` (모든 데이터그램). | +|------|--------|------| +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 전달된 shred의 대상 (반복 가능). | +| `DZ_SHRED_DISABLE` | `0` | 마스터 옵트아웃 (`--shred-forward-disable`). 인가에서 허용하는 것과 관계없이 포워더를 비활성화합니다 — 로컬 소비자가 리스닝하지 않을 때 설정하여, shred 파이어호스를 아무 곳에도 전달하지 않는 데 CPU를 소모하는 것을 방지합니다. | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (shred당 하나의 복사본), `sigverify` (+ ed25519 검증), `none` (모든 데이터그램). | | `DZ_SHRED_RPC_URL` | — | Solana RPC 엔드포인트; `sigverify` 모드에 필요합니다. | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 중복 제거 윈도우 크기. | -전체 파이프라인과 주의사항은 [시레드 포워딩](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)을 참조하세요. +전체 파이프라인과 주의 사항은 [Shred 전달](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)을 참조하세요. --- ## 시장 데이터 WebSocket -`ws://:8081`에 WebSocket을 연결하고 JSON 프레임을 읽으세요. 인가된 모든 거래소의 데이터를 수신합니다. 선택적 `subscribe` 메시지를 통해 스트림을 특정 거래소와 심볼로 좁힐 수 있습니다. +`ws://:8081`로 WebSocket을 열고 JSON 프레임을 읽으세요. 인가된 모든 거래소의 데이터를 수신합니다. 선택적 `subscribe` 메시지로 스트림을 특정 거래소와 심볼로 좁힐 수 있습니다. -WebSocket + JSON을 지원하는 모든 엔진은 간단한 (~50–100줄) 어댑터로 이를 소비할 수 있습니다. 바이너리 멀티캐스트, 거래소별 2포트 분할, 매니페스트/정밀도 핸드셰이크는 모두 브리지 내부에서 처리됩니다. 소비자가 코딩해야 할 유일한 계약은 WebSocket JSON입니다. +WebSocket + JSON을 사용하는 모든 엔진이 간단한(~50–100줄) 어댑터로 소비할 수 있습니다. 바이너리 멀티캐스트, 거래소별 2포트 분리, 매니페스트/정밀도 핸드셰이크는 모두 브리지 내부에 있으며, 소비자가 구현해야 하는 유일한 인터페이스는 WebSocket JSON입니다. -### 연결 라이프사이클 +!!! note + WebSocket 싱크는 인가에 대해 하나 이상의 시장 데이터 피드가 활성화된 경우에만 시작됩니다 — shred 전용 호스트는 WebSocket을 제공하지 않습니다. 활성화는 30초마다 갱신되는 온체인 구독 조정기(`--subscription-refresh-secs`)에 의해 구동됩니다; `--subscription-gating-disable`는 게이팅을 옵트아웃합니다. + +### 연결 수명 주기 새 연결마다 브리지는: -1. **현재 상품 정의를 재전송합니다** — 알려진 심볼당 하나의 `instrument` 메시지 — 소비자가 첫 번째 호가 전에 정밀도를 파악할 수 있도록 합니다. -2. **심볼당 최신 호가창 스냅샷을 재전송합니다** (Market-by-Order 피드가 활성인 경우). -3. `quote` / `trade` / `midpoint` / `depth` 메시지를 도착하는 대로 **스트리밍**하며 연결된 모든 소비자에게 팬아웃합니다. +1. **현재 상품 정의를 재생합니다** — 알려진 심볼당 하나의 `instrument` 메시지 — 소비자가 첫 호가 전에 정밀도를 확보할 수 있도록 합니다. +2. **심볼별 최신 호가 잔량 스냅샷을 재생합니다** (Market-by-Order 피드가 활성화된 경우). +3. `quote` / `trade` / `midpoint` / `depth` 메시지를 도착하는 대로 **스트리밍**하며, 연결된 모든 소비자에게 팬아웃합니다. ``` -connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … +connect → instrument (×N) → depth (×M, 최신 호가잔량) → quote → trade → depth → … ``` ### 메시지 유형 @@ -175,12 +184,12 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade 모든 메시지는 `type` 필드로 태그된 JSON 객체입니다: | `type` | 의미 | -|--------|---------| +|--------|------| | `instrument` | 상품/정밀도 정의. | | `quote` | 최우선 호가 업데이트 (전체 상태). | -| `trade` | 체결 내역 (최근 거래). | +| `trade` | 체결 (최종 거래). | | `midpoint` | 파생 중간 가격. | -| `depth` | 전체 호가창 깊이 스냅샷. | +| `depth` | 전체 호가 잔량 스냅샷. | | `status` | 거래소 수준 피드 상태 전환. | 소비자는 **알 수 없는 `type` 값과 알 수 없는 필드를 무시해야 합니다** (전방 호환성). @@ -191,7 +200,7 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -연결 시와 정의가 변경될 때마다 전송됩니다. `price_exponent`와 `qty_exponent`는 거래소의 틱 사이즈와 수량 단위를 10의 거듭제곱으로 나타냅니다. +연결 시 및 정의 변경 시 전송됩니다. `price_exponent`와 `qty_exponent`는 거래소의 틱 크기와 수량 단위를 10의 거듭제곱으로 나타냅니다. #### `quote` @@ -210,14 +219,14 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade } ``` -모든 `quote`는 **전체 상태**입니다 — 메시지가 손실되어도 다음 호가에서 자동 복구되며, 재동기화가 필요 없습니다. 네 개의 타임스탬프는 종단 간 지연 시간을 분해합니다: +모든 `quote`는 **전체 상태**입니다 — 메시지가 누락되어도 다음 호가에서 자동 복구되며, 재동기화가 필요 없습니다. 네 개의 타임스탬프는 엔드투엔드 지연 시간을 분해합니다: ``` -source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) - venue book wire arrival post-decode WS hand-off +source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (소비자 수신) + 거래소 호가 와이어 도착 디코딩 후 WS 핸드오프 ``` -`0`은 "사용할 수 없음"을 나타내는 센티넬 값입니다 — 1970이 아닌 누락된 값으로 처리하세요. +`0`은 "사용 불가"의 센티넬 값입니다 — 1970이 아닌 누락으로 처리하세요. #### `trade` @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`aggressor_side`는 `"buy"`, `"sell"` 또는 `"unknown"`입니다. 체결은 시점 이벤트이며 재연결 시 재전송되지 않습니다. +`aggressor_side`는 `"buy"`, `"sell"`, 또는 `"unknown"`입니다. 거래는 시점 이벤트이며 재연결 시 재생되지 않습니다. #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`bids`는 최고가 우선으로 정렬되고 `asks`는 최저가 우선으로 정렬됩니다. 각 `depth`는 **전체 스냅샷**입니다 — 병합하지 말고 교체하세요. +`bids`는 높은 가격 순으로 정렬됩니다; `asks`는 낮은 가격 순으로 정렬됩니다. 각 `depth`는 **전체 스냅샷**입니다 — 병합하지 말고 교체하세요. #### `status` @@ -256,32 +265,32 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -거래소의 호가 멀티캐스트가 침묵할 때(`state:"down"`) 또는 복구될 때(`state:"ok"`) Edge에서 발생합니다. UI에서 거래소를 비활성 상태로 표시하는 데 사용하세요. 호가 전달은 상태에 의해 차단되지 않습니다 — 피드는 다음 호가에서 자동 복구됩니다. +거래소의 호가 멀티캐스트가 침묵할 때(`state:"down"`) 또는 복구될 때(`state:"ok"`) 에지에서 발행됩니다. UI에서 거래소를 비활성화 표시하는 데 사용하세요. 호가 전달은 상태에 의해 게이팅되지 않습니다 — 피드는 다음 호가에서 자동 복구됩니다. ### 구독 -기본적으로 모든 것을 수신합니다. 스트림을 좁히려면 제어 메시지를 보내세요: +기본적으로 모든 데이터를 수신합니다. 스트림을 좁히려면 제어 메시지를 전송하세요: ```json {"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} {"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -필드를 생략하면 모든 값과 일치합니다(`{"symbol":"SOL"}` = 모든 거래소의 SOL). `venue`는 대소문자를 구분하지 않고 매칭됩니다. +필드를 생략하면 모든 값과 일치합니다 (`{"symbol":"SOL"}` = 모든 거래소의 SOL). `venue`는 대소문자를 구분하지 않고 매칭됩니다. -**서버 확인 응답:** +**서버 응답:** ```json {"channel":"subscription_response","method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -오류는 `{"channel":"error","error":""}`으로 반환됩니다. +오류는 `{"channel":"error","error":""}`를 반환합니다. ### 하트비트 및 활성 확인 -- 서버는 20초마다 **WebSocket Ping**을 보냅니다. 호환 클라이언트는 자동으로 Pong을 응답합니다. -- 60초 동안 비활성인 클라이언트는 닫히고 정리됩니다. -- 애플리케이션 수준 keepalive: `{"method":"ping"}` → `{"channel":"pong"}`. +- 서버는 20초마다 **WebSocket Ping**을 전송합니다; 호환 클라이언트는 자동으로 Pong을 응답합니다. +- 60초 동안 무응답인 클라이언트는 종료 및 정리됩니다. +- 앱 수준 킵얼라이브: `{"method":"ping"}` → `{"channel":"pong"}`. ### 소비자 스켈레톤 @@ -304,22 +313,30 @@ def on_message(ws, frame): elif t == "depth": replace_book(msg["venue"], msg["symbol"], msg["bids"], msg["asks"]) - # unknown types: silently ignore (forward compatibility) + # 알 수 없는 타입: 조용히 무시 (전방 호환성) ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() ``` -### 입력 소스와 WebSocket 백스톱 +### 입력 소스 및 WebSocket 백스톱 + +Edge 멀티캐스트 피드는 상시 활성화됩니다. 선택적 **공개 WebSocket 백스톱**은 Edge 피드가 중단될 때 갭을 채울 수 있습니다. 두 가지가 있으며, 각각 기본적으로 비활성화되고 거래소별로 독립적으로 활성화됩니다: -Edge 멀티캐스트 피드는 항상 활성 상태입니다. 선택적 **공개 WebSocket 백스톱**은 Edge 피드가 중단될 때 격차를 메울 수 있습니다: +| 백스톱 | 활성화 방법 | 커버 범위 | 기본 URL | +|--------|------------|----------|----------| +| **Hyperliquid** | `WS_INPUT_COINS` (예: `BTC,ETH`) | 호가 + 거래 | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (티커, 예: `SOL,BTC`) | **거래만** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# BTC와 ETH에 대한 백스톱 활성화: +# BTC과 ETH에 대한 Hyperliquid 백스톱 활성화: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# SOL에 대한 Phoenix 거래 백스톱 활성화: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -두 소스는 공유 아비터 내에서 `(venue, symbol, source_ts)` 틱 단위로 경쟁합니다. 정상 상태에서는 Edge 소스가 승리합니다(인터넷 경유 수십 ms 대비 1ms 미만). Edge에 격차가 발생하면 공개 복사본이 채웁니다. WebSocket 출력은 어떤 소스가 특정 업데이트를 전달했는지와 관계없이 동일합니다. +각 `(venue, symbol, source_ts)` 틱에 대해, Edge와 공개 소스가 공유 중재기 내에서 경쟁합니다. 정상 상태에서는 Edge 소스가 승리합니다(인터넷 상의 수십 ms 대비 1ms 미만); Edge에 갭이 발생하면 공개 복사본이 채웁니다. WebSocket 출력은 어떤 소스가 특정 업데이트를 전달했는지에 관계없이 동일합니다. (Phoenix 백스톱은 거래만 제공합니다 — Edge가 Phoenix 호가의 유일한 소스로 남습니다.) --- @@ -346,91 +363,16 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne ## 모니터링 (Prometheus 메트릭) -메트릭 엔드포인트는 **기본적으로 비활성**입니다. `METRICS_BIND`로 활성화하세요: +메트릭 엔드포인트는 **기본적으로 비활성화**되어 있습니다. `METRICS_BIND`로 활성화하세요: ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -그런 다음 스크래핑하세요: +그런 다음 스크레이핑: ```bash curl -s localhost:9090/metrics | grep '^dz_' ``` -주요 메트릭: - -| 메트릭 | 추적 대상 | -|--------|---------------| -| `dz_feed_up{venue}` | 해당 거래소의 멀티캐스트가 활성이면 `1`, 침묵이면 `0`. | -| `dz_datagrams_received_total{venue}` | 거래소별 수집 볼륨. | -| `dz_emit_total{venue,kind}` | 중복 제거 후 유형별 브로드캐스트된 메시지. | -| `dz_quotes_dropped_total{venue}` | 억제된 오래된/중복 호가. | -| `dz_ws_clients` | 현재 연결된 WebSocket 클라이언트 수. | -| `dz_ws_messages_sent_total{kind}` | 클라이언트에 포워딩된 메시지. | -| `dz_ws_client_lagged_total` | 피드 보호를 위해 느린 클라이언트가 제거된 횟수. | - -동일한 바인드 주소에 `GET /healthz` 활성 프로브도 제공됩니다. - ---- - -## 고급: 자체 호스팅 - -컨테이너는 GHCR에서 사용할 수 있습니다: - -| 환경 | 이미지 | 태그 | -|-------------|-------|-----| -| Mainnet-beta | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:mainnet-beta` | -| Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | -| Devnet (비공개) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | - -수동으로 실행합니다(설치 프로그램이 전달할 수 없는 옵션, 예: `WS_BIND=""`에 필요): - -```bash -docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ - -e DZ_SECRET=DZ_… \ - -e DZ_SHRED_FORWARD=127.0.0.1:20000 \ - -e WS_BIND=0.0.0.0:8081 \ - -e METRICS_BIND=127.0.0.1:9090 \ - ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta -``` - -**소스에서 빌드:** - -```bash -git clone https://github.com/malbeclabs/doublezero-edge-connect -cd doublezero-edge-connect -cargo build --release -cargo test - -./target/release/doublezero-edge-connect \ - --iface doublezero1 \ - --ws-bind 0.0.0.0:8081 -``` - -버스트 피드에는 더 큰 커널 수신 버퍼가 권장됩니다: - -```bash -sudo sysctl -w net.core.rmem_max=268435456 -``` - ---- - -## 제한 및 백프레셔 - -| 제한 | 기본값 | 초과 시 동작 | -|-------|---------|------------------------| -| 동시 클라이언트 수 (`WS_MAX_CLIENTS`) | 64 | 새 연결이 거부됩니다. | -| 클라이언트당 구독 수 (`WS_MAX_SUBS`) | 256 | `subscribe`가 오류와 함께 거부됩니다. | -| 클라이언트당 분당 인바운드 제어 메시지 수 (`WS_MAX_INBOUND_PER_MIN`) | 600 | 클라이언트 연결이 끊깁니다. | -| 브로드캐스트 버퍼 (`WS_BROADCAST_CAPACITY`) | 4096 | 느린 클라이언트는 **가장 오래된 메시지를 드롭합니다** (피드를 차단하지 않습니다). | - -모든 `quote`와 `depth`는 전체 상태이므로, 백프레셔로 인해 메시지를 드롭한 소비자도 다음 메시지에서 자동 복구됩니다 — 재동기화 핸드셰이크가 필요 없습니다. - ---- - -## 문제 해결 - -### 로컬 포트에 시레드가 도착하지 않음 - -- 온체인에서 `edge-solana-*` 시레드 그룹에 대한 접근이 인가 \ No newline at end of file +주요 \ No newline at end of file diff --git a/docs/Edge Market Data Connection.pt.md b/docs/Edge Market Data Connection.pt.md index 75eb469..a94ce74 100644 --- a/docs/Edge Market Data Connection.pt.md +++ b/docs/Edge Market Data Connection.pt.md @@ -1,17 +1,17 @@ --- -description: Execute o doublezero-edge-connect para reencaminhar shreds Solana para uma porta UDP local e consumir dados de mercado normalizados do Edge através de um WebSocket local. +description: Execute o doublezero-edge-connect para reencaminhar shreds Solana para uma porta UDP local e consumir dados de mercado Edge normalizados através de um WebSocket local. --- # Conexão Edge -!!! warning "Ao conectar-me ao DoubleZero, concordo com os [Termos de Uso do DoubleZero](https://doublezero.xyz/terms-protocol). Os dados são apenas para uso interno e não podem ser retransmitidos (consulte a Secção 2(e))." +!!! warning "Ao conectar-me ao DoubleZero, concordo com os [Termos de Uso do DoubleZero](https://doublezero.xyz/terms-protocol). Os dados são apenas para uso interno e não podem ser retransmitidos (ver Secção 2(e))." `doublezero-edge-connect` é uma ponte que se junta ao **multicast binário do DoubleZero Edge** e o re-serve localmente como dois feeds: 1. **Encaminhamento de shreds Solana** — shreds deduplicados (opcionalmente com verificação de assinatura) distribuídos para um ou mais destinos UDP locais, prontos para o seu validador ou RPC. 2. **Dados de mercado normalizados** — feeds de venues Edge decodificados, com precisão corrigida, e re-servidos como um único WebSocket JSON em `ws://host:8081`. -Ambos executam a partir do mesmo contêiner e da mesma instalação de uma linha. Ative os feeds que a sua autorização onchain conceder. +Ambos executam a partir do mesmo container e da mesma instalação de uma linha. Ative os feeds que a sua autorização onchain permitir. ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -25,15 +25,15 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## Requisitos - Host **Linux/amd64** com um endereço IPv4 público autorizado onchain para o ambiente alvo. -- **Docker** (o comando de uma linha instala-o se estiver ausente). -- **Conectividade GRE** — permita o protocolo IP 47 no seu provedor cloud; na AWS desative a verificação source/dest da ENI. +- **Docker** (o comando de uma linha instala-o se estiver em falta). +- **Conectividade GRE** — permita o protocolo IP 47 no seu fornecedor de cloud; na AWS desative a verificação source/dest do ENI. - Um **segredo de acesso DoubleZero**: um token base64 com prefixo `DZ_` ou um caminho para um ficheiro de keypair, obtido no processo de [onboarding DoubleZero](setup.md). --- ## Passo 1: Instalar e Executar -Um único comando prepara o host e inicia o contêiner da ponte. Ele junta-se à rede DoubleZero e inicia todos os feeds que a sua autorização conceder — encaminhamento de shreds e/ou o WebSocket de dados de mercado na porta `:8081`: +Um único comando prepara o host e inicia o container da ponte. Junta-se à rede DoubleZero e inicia todos os feeds que a sua autorização concede — encaminhamento de shreds e/ou o WebSocket de dados de mercado na porta `:8081`: === "Mainnet-beta" @@ -56,13 +56,13 @@ Um único comando prepara o host e inicia o contêiner da ponte. Ele junta-se à O que o script faz: -1. Verifica que o host é Linux/amd64, garante que o Docker está presente (oferece instalá-lo). -2. Prepara o kernel do host para o túnel GRE: carrega `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avisa sobre regras de firewall e do provedor cloud. -3. Carrega o seu segredo de acesso (solicitado uma vez se `DZ_SECRET` não estiver definido). -4. Executa o contêiner da ponte (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) e executa `doublezero connect multicast`. +1. Verifica que o host é Linux/amd64. +2. Carrega o seu segredo de acesso (solicitado uma vez se `DZ_SECRET` não estiver definido) e **verifica o passe de acesso onchain antes de instalar qualquer coisa** — uma verificação pura do lado do host contra o JSON-RPC público do ledger. Se o passe estiver vinculado a um IP diferente do host, aborta (quando o IP foi dado explicitamente via `DZ_CLIENT_IP`) ou avisa e continua (quando o IP foi apenas auto-detetado, o que pode estar errado atrás de NAT), deixando `doublezero connect` como a verificação real. +3. Garante que o Docker está presente (oferece-se para instalá-lo) e prepara o kernel do host para o túnel GRE: carrega `tun`/`ip_gre`, aumenta `net.core.rmem_max`, avisa sobre regras de firewall e fornecedor de cloud. +4. Executa o container da ponte (`--network host`, `NET_ADMIN`/`NET_RAW`, `/dev/net/tun`) e executa `doublezero connect multicast`. -!!! tip "Instalação não interativa" - Defina `DZ_SECRET=DZ_…` antes do pipe para executar completamente sem supervisão — sem prompts. +!!! tip "Instalação não-interativa" + Defina `DZ_SECRET=DZ_…` antes do pipe para executar completamente sem supervisão — sem prompts de todo. --- @@ -78,29 +78,34 @@ DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash | Variável | Padrão | Finalidade | |----------|--------|------------| -| `DZ_SECRET` | *(solicitado)* | Token base64 com prefixo `DZ_` **ou** caminho para um ficheiro de keypair. Um token é injetado no contêiner e nunca escrito em disco; um ficheiro é montado em modo somente leitura. | +| `DZ_SECRET` | *(solicitado)* | Token base64 com prefixo `DZ_` **ou** caminho para um ficheiro de keypair. Um token é injetado no container e nunca escrito em disco; um ficheiro é montado em bind como apenas leitura. | | `DZ_ENV` | por script | `mainnet-beta` \| `testnet` \| `devnet`. | -| `DZ_IMAGE` | por script | Substituir a imagem do contêiner. | -| `DZ_NAME` | `doublezero-edge-connect` | Nome do contêiner. | +| `DZ_IMAGE` | por script | Substituir a imagem do container. | +| `DZ_NAME` | `doublezero-edge-connect` | Nome do container. | | `DZ_FEEDS` | *(todos)* | Venues separadas por vírgula para restringir a ingestão de dados de mercado (ex.: `VenueA,VenueB`). Não afeta o encaminhamento de shreds Solana. | +| `DZ_CLIENT_IP` | *(auto-detetado)* | Substituir o IPv4 público usado pela pré-verificação do passe de acesso onchain. Defina-o quando a auto-deteção estiver errada (ex.: atrás de NAT) para que a pré-verificação possa confirmar em vez de apenas avisar. | +| `DZ_LEDGER_RPC_URL` | por env | Substituir o endpoint RPC do ledger DoubleZero usado pela pré-verificação. | | `DZ_ASSUME_YES` | `0` | Ignorar prompts de confirmação (ex.: o prompt de instalação do Docker). | | `DZ_GHCR_TOKEN` | — | **Apenas Devnet** — um token GHCR com `read:packages` (a imagem devnet é privada). | | `DZ_GHCR_USER` | `malbeclabs` | **Apenas Devnet** — nome de utilizador GHCR para o login. | ### Variáveis da ponte -O instalador encaminha **qualquer variável de ponte não vazia** diretamente para o contêiner. As mais comuns: +O instalador encaminha **qualquer** variável da ponte **não vazia** diretamente para o container. As mais comuns: | Variável | Padrão | Finalidade | |----------|--------|------------| | `DZ_IFACE` | `doublezero1` | Interface de rede para escutar. | -| `DZ_RECV_BUF` | — | Substituição do buffer de receção UDP (bytes). | -| `METRICS_BIND` | *(vazio / desativado)* | Ativar o endpoint Prometheus `/metrics` (ex.: `127.0.0.1:9090`). | -| `RUST_LOG` | `info` | Nível de log (`debug`, `warn`, etc.). | -| `DZ_SHRED_FORWARD` | — | Destino(s) UDP local(ais) para shreds encaminhados — veja [Encaminhamento de Shreds Solana](#encaminhamento-de-shreds-solana). | -| `WS_BIND` | `0.0.0.0:8081` | Endereço de bind do WebSocket de dados de mercado — veja [WebSocket de Dados de Mercado](#websocket-de-dados-de-mercado). | +| `DZ_RECV_BUF` | `8388608` | Substituição do buffer de receção UDP (bytes; padrão 8 MiB). | +| `METRICS_BIND` | *(vazio / desligado)* | Ativar o endpoint Prometheus `/metrics` (ex.: `127.0.0.1:9090`). | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | Nível de log (`debug`, `warn`, etc.). | +| `DZ_SHRED_FORWARD` | — | Destino(s) UDP local(is) para shreds encaminhados — ver [Encaminhamento de Shreds Solana](#encaminhamento-de-shreds-solana). | +| `WS_BIND` | `0.0.0.0:8081` | Endereço de bind do WebSocket de dados de mercado — ver [WebSocket de Dados de Mercado](#websocket-de-dados-de-mercado). | | `WS_MAX_CLIENTS` | `64` | Máximo de clientes WebSocket simultâneos. | -| `WS_INPUT_COINS` | *(vazio / desativado)* | Ativar o backstop público via WebSocket para os símbolos listados (ex.: `BTC,ETH`). | +| `WS_INPUT_COINS` | *(vazio / desligado)* | Ativar o backstop do WebSocket público Hyperliquid para símbolos listados (ex.: `BTC,ETH`) — ver [Fontes de entrada](#fontes-de-entrada-e-o-backstop-websocket). | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | URL do WebSocket público Hyperliquid para o backstop. | +| `PHOENIX_WS_INPUT_MARKETS` | *(vazio / desligado)* | Ativar o backstop do WebSocket público Phoenix (apenas trades) para tickers listados (ex.: `SOL,BTC`). | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | URL do WebSocket público Phoenix para o backstop. | **Exemplos:** @@ -109,10 +114,10 @@ O instalador encaminha **qualquer variável de ponte não vazia** diretamente pa DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# Não interativo, testnet: +# Não-interativo, testnet: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# Restringir dados de mercado a venues específicas, logging verboso, porta WS não padrão: +# Restringir dados de mercado a venues específicas, logging verboso, porta WS não-padrão: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash @@ -122,13 +127,13 @@ DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ ``` !!! note - Como o instalador só encaminha valores **não vazios**, não é possível passar uma substituição vazia (ex.: `WS_BIND=""` para desativar o sink WebSocket) através do comando de uma linha. Use um `docker run` escrito manualmente para isso — veja [Self-hosting](#avancado-self-hosting). + O instalador encaminha apenas valores **não vazios**, com uma exceção: `WS_BIND` é encaminhado mesmo quando definido como vazio, portanto `WS_BIND=""` **desativa** efetivamente o sink WebSocket através do comando de uma linha. Para qualquer outra variável, uma substituição vazia não pode ser passada pelo pipe — use um `docker run` escrito manualmente para isso (ver [Self-hosting](#avancado-self-hosting)). --- ## Encaminhamento de Shreds Solana -A ponte junta-se aos grupos multicast `edge-solana-*` de shreds e distribui cada datagrama para um ou mais destinos UDP locais — alimentando o seu validador ou RPC diretamente a partir da rede Edge. Ativa-se automaticamente na descoberta quando esses grupos estão presentes na sua autorização. +A ponte junta-se aos grupos multicast de shreds `edge-solana-*` e distribui cada datagrama para um ou mais destinos UDP locais — alimentando o seu validador ou RPC diretamente a partir da rede Edge. Ativa-se automaticamente na descoberta quando esses grupos estão presentes na sua autorização. ```bash # Padrão (apenas dedup, encaminhar para porta local 20000): @@ -144,27 +149,31 @@ DZ_SHRED_DEDUP_MODE=sigverify \ | Variável | Padrão | Finalidade | |----------|--------|------------| | `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | Destino(s) para shreds encaminhados (repetível). | +| `DZ_SHRED_DISABLE` | `0` | Desativação principal (`--shred-forward-disable`). Mantém o encaminhador desligado independentemente do que a sua autorização concede — defina-o quando nenhum consumidor local está a escutar, para evitar gastar CPU a encaminhar o fluxo de shreds para lugar nenhum. | | `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup` (uma cópia por shred), `sigverify` (+ verificação ed25519), `none` (todos os datagramas). | | `DZ_SHRED_RPC_URL` | — | Endpoint RPC Solana; necessário pelo modo `sigverify`. | -| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Tamanho da janela de deduplicação. | +| `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | Tamanho da janela de dedup. | -Veja [Encaminhamento de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para o pipeline completo e ressalvas. +Ver [Encaminhamento de shreds](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) para o pipeline completo e ressalvas. --- ## WebSocket de Dados de Mercado -Abra um WebSocket para `ws://:8081` e leia frames JSON. Recebe todas as venues para as quais está autorizado. Uma mensagem opcional `subscribe` restringe o fluxo a venues e símbolos específicos. +Abra um WebSocket para `ws://:8081` e leia frames JSON. Recebe todas as venues para as quais está autorizado. Uma mensagem `subscribe` opcional restringe o stream a venues e símbolos específicos. -Qualquer engine que fale WebSocket + JSON pode consumi-lo com um adaptador simples (~50–100 linhas). O multicast binário, a divisão em duas portas por venue, e o handshake de manifesto/precisão ficam todos dentro da ponte; o único contrato contra o qual um consumidor programa é o WebSocket JSON. +Qualquer engine que fale WebSocket + JSON pode consumi-lo com um adaptador fino (~50–100 linhas). O multicast binário, a divisão de duas portas por venue, e o handshake de manifesto/precisão ficam todos dentro da ponte; o único contrato contra o qual um consumidor programa é o WebSocket JSON. + +!!! note + O sink WebSocket inicia apenas quando pelo menos um feed de dados de mercado está ativo para a sua autorização — um host apenas de shreds não serve WebSocket. A ativação é conduzida por um reconciliador de subscrições onchain que atualiza a cada 30s (`--subscription-refresh-secs`); `--subscription-gating-disable` desativa o gating. ### Ciclo de vida da conexão Em cada nova conexão, a ponte: -1. **Repete as definições de instrumentos atuais** — uma mensagem `instrument` por símbolo conhecido — para que o consumidor tenha a precisão antes da primeira cotação. -2. **Repete o último snapshot de profundidade** por símbolo (se o feed Market-by-Order estiver ativo). -3. **Transmite** mensagens `quote` / `trade` / `midpoint` / `depth` à medida que chegam, distribuídas a todos os consumidores conectados. +1. **Reproduz as definições de instrumentos atuais** — uma mensagem `instrument` por símbolo conhecido — para que o consumidor tenha a precisão antes da primeira cotação. +2. **Reproduz o último snapshot de profundidade** por símbolo (se o feed Market-by-Order estiver ativo). +3. **Transmite** mensagens `quote` / `trade` / `midpoint` / `depth` à medida que chegam, distribuídas para todos os consumidores conectados. ``` connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … @@ -178,10 +187,10 @@ Cada mensagem é um objeto JSON identificado por um campo `type`: |--------|-------------| | `instrument` | Definição de instrumento/precisão. | | `quote` | Atualização do topo do livro (estado completo). | -| `trade` | Impressão de negócio (última venda). | +| `trade` | Impressão de trade (última venda). | | `midpoint` | Preço médio derivado. | -| `depth` | Snapshot completo de profundidade do livro de ordens. | -| `status` | Transição de saúde do feed a nível de venue. | +| `depth` | Snapshot completo da profundidade do livro de ordens. | +| `status` | Transição de saúde do feed ao nível da venue. | Os consumidores **devem ignorar valores `type` desconhecidos e campos desconhecidos** (compatibilidade futura). @@ -191,7 +200,7 @@ Os consumidores **devem ignorar valores `type` desconhecidos e campos desconheci {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -Enviado na conexão e sempre que as definições mudam. `price_exponent` e `qty_exponent` indicam o tick size e o step de tamanho da venue como potências de dez. +Enviado na conexão e sempre que as definições mudam. `price_exponent` e `qty_exponent` fornecem o tick size e o size step da venue como potências de dez. #### `quote` @@ -210,7 +219,7 @@ Enviado na conexão e sempre que as definições mudam. `price_exponent` e `qty_ } ``` -Cada `quote` é **estado completo** — uma mensagem perdida auto-recupera na próxima cotação, sem necessidade de ressincronização. Os quatro timestamps decompõem a latência de ponta a ponta: +Cada `quote` é **estado completo** — uma mensagem perdida auto-recupera na próxima cotação, sem necessidade de resincronização. Os quatro timestamps decompõem a latência ponta-a-ponta: ``` source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) @@ -233,7 +242,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`aggressor_side` é `"buy"`, `"sell"` ou `"unknown"`. Negócios são eventos pontuais no tempo e não são repetidos na reconexão. +`aggressor_side` é `"buy"`, `"sell"`, ou `"unknown"`. Trades são eventos pontuais e não são reproduzidos na reconexão. #### `depth` @@ -248,7 +257,7 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer } ``` -`bids` são ordenados do preço mais alto para o mais baixo; `asks` são ordenados do preço mais baixo para o mais alto. Cada `depth` é um **snapshot completo** — substitua, não faça merge. +`bids` são ordenados pelo preço mais alto primeiro; `asks` são ordenados pelo preço mais baixo primeiro. Cada `depth` é um **snapshot completo** — substitua, não faça merge. #### `status` @@ -256,11 +265,11 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -Emitido no edge quando o multicast de cotações de uma venue fica silencioso (`state:"down"`) ou recupera (`state:"ok"`). Use-o para desativar visualmente uma venue na sua UI. A entrega de cotações não depende do status — o feed auto-recupera na próxima cotação. +Emitido no edge quando o multicast de cotações de uma venue fica silencioso (`state:"down"`) ou recupera (`state:"ok"`). Use-o para desativar visualmente uma venue na sua UI. A entrega de cotações não é condicionada pelo status — o feed auto-recupera na próxima cotação. ### Subscrições -Por padrão, recebe tudo. Envie uma mensagem de controlo para restringir o fluxo: +Por defeito recebe tudo. Envie uma mensagem de controlo para restringir o stream: ```json {"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} @@ -280,10 +289,10 @@ Erros retornam `{"channel":"error","error":""}`. ### Heartbeat e liveness - O servidor envia um **WebSocket Ping** a cada 20 segundos; clientes conformes respondem automaticamente com Pong. -- Clientes silenciosos por 60 segundos são fechados e eliminados. -- Keepalive a nível de aplicação: `{"method":"ping"}` → `{"channel":"pong"}`. +- Clientes silenciosos durante 60 segundos são fechados e descartados. +- Keepalive ao nível da aplicação: `{"method":"ping"}` → `{"channel":"pong"}`. -### Esqueleto do consumidor +### Esqueleto de consumidor ```python import json, websocket @@ -304,7 +313,7 @@ def on_message(ws, frame): elif t == "depth": replace_book(msg["venue"], msg["symbol"], msg["bids"], msg["asks"]) - # unknown types: silently ignore (forward compatibility) + # tipos desconhecidos: ignorar silenciosamente (compatibilidade futura) ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() @@ -312,24 +321,32 @@ ws.run_forever() ### Fontes de entrada e o backstop WebSocket -O feed multicast Edge está sempre ativo. Um **backstop público via WebSocket** opcional pode preencher lacunas quando o feed Edge estagna: +O feed multicast Edge está sempre ativo. **Backstops de WebSocket público** opcionais podem preencher lacunas quando o feed Edge fica parado. Dois estão disponíveis, cada um desligado por defeito e ativado independentemente por venue: + +| Backstop | Ativar com | Cobre | URL padrão | +|----------|------------|-------|------------| +| **Hyperliquid** | `WS_INPUT_COINS` (ex.: `BTC,ETH`) | cotações + trades | `wss://api.hyperliquid.xyz/ws` (`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS` (tickers, ex.: `SOL,BTC`) | **apenas trades** | `wss://perp-api.phoenix.trade/v1/ws` (`PHOENIX_WS_INPUT_URL`) | ```bash -# Ativar o backstop para BTC e ETH: +# Ativar o backstop Hyperliquid para BTC e ETH: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# Ativar o backstop de trades Phoenix para SOL: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -As duas fontes competem por tick `(venue, symbol, source_ts)` dentro de um árbitro partilhado. Em estado estável, a fonte Edge vence (sub-ms vs. dezenas de ms pela internet); quando o Edge falha, a cópia pública preenche. A saída WebSocket é idêntica independentemente de qual fonte entregou uma determinada atualização. +Para cada tick `(venue, symbol, source_ts)`, as fontes Edge e pública competem dentro de um árbitro partilhado. Em estado estável, a fonte Edge ganha (sub-ms vs. dezenas de ms pela internet); quando o Edge falha, a cópia pública preenche. A saída WebSocket é idêntica independentemente de qual fonte entregou uma dada atualização. (Os backstops Phoenix cobrem apenas trades — o Edge permanece a única fonte de cotações Phoenix.) --- -## Gerir o Contêiner +## Gerir o Container ```bash # Transmitir logs sudo docker logs -f doublezero-edge-connect -# Verificar o estado do túnel +# Verificar estado do túnel sudo docker exec -it doublezero-edge-connect doublezero status # Verificar latências do dispositivo @@ -340,13 +357,13 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne ``` !!! note "Sem TLS" - A ponte destina-se a uma rede confiável/local. Termine o TLS num reverse proxy se expuser o endpoint WebSocket externamente. + A ponte destina-se a uma rede de confiança/local. Termine TLS num reverse proxy se expuser o endpoint WebSocket externamente. --- ## Monitorização (Métricas Prometheus) -O endpoint de métricas está **desativado por padrão**. Ative-o com `METRICS_BIND`: +O endpoint de métricas está **desligado por defeito**. Ative-o com `METRICS_BIND`: ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash @@ -360,11 +377,12 @@ curl -s localhost:9090/metrics | grep '^dz_' Métricas principais: -| Métrica | O que monitoriza | -|---------|------------------| +| Métrica | O que rastreia | +|---------|---------------| | `dz_feed_up{venue}` | `1` enquanto o multicast dessa venue está ativo, `0` enquanto silencioso. | | `dz_datagrams_received_total{venue}` | Volume de ingestão por venue. | -| `dz_emit_total{venue,kind}` | Mensagens transmitidas após dedup, por tipo. | +| `dz_emit_total{venue,kind}` | Mensagens emitidas após dedup, por tipo. | +| `dz_quotes_admitted_total{venue,publisher}` | Cotações admitidas pelo árbitro, atribuídas à fonte vencedora. Um aumento em `publisher="public"` significa que um backstop está a preencher uma lacuna do Edge (vs. `publisher="edge"` em estado estável). | | `dz_quotes_dropped_total{venue}` | Cotações obsoletas/duplicadas suprimidas. | | `dz_ws_clients` | Clientes WebSocket atualmente conectados. | | `dz_ws_messages_sent_total{kind}` | Mensagens encaminhadas para clientes. | @@ -376,7 +394,7 @@ Uma sonda de liveness `GET /healthz` também é servida no mesmo endereço de bi ## Avançado: Self-hosting -O contêiner está disponível no GHCR: +O container está disponível no GHCR: | Ambiente | Imagem | Tag | |----------|--------|-----| @@ -408,7 +426,7 @@ cargo test --ws-bind 0.0.0.0:8081 ``` -Um buffer de receção do kernel maior é recomendado para feeds com rajadas: +Um buffer de receção do kernel maior é recomendado para feeds com picos: ```bash sudo sysctl -w net.core.rmem_max=268435456 @@ -425,7 +443,7 @@ sudo sysctl -w net.core.rmem_max=268435456 | Msgs de controlo de entrada / cliente / min (`WS_MAX_INBOUND_PER_MIN`) | 600 | Cliente é desconectado. | | Buffer de broadcast (`WS_BROADCAST_CAPACITY`) | 4096 | Um cliente lento **descarta as mensagens mais antigas** (nunca bloqueia o feed). | -Como cada `quote` e `depth` é estado completo, um consumidor que perca mensagens sob contrapressão auto-recupera na próxima mensagem — sem necessidade de handshake de ressincronização. +Como cada `quote` e `depth` é estado completo, um consumidor que perca mensagens sob contrapressão auto-recupera na próxima mensagem — sem necessidade de handshake de resincronização. --- @@ -445,21 +463,21 @@ Como cada `quote` e `depth` é estado completo, um consumidor que perca mensagen - Confirme que o seu acesso está autorizado para essa venue onchain. - Restrinja a ingestão a essa venue com `DZ_FEEDS=` para isolar o problema. -### WebSocket conecta mas nenhuma cotação chega +### WebSocket conecta mas não chegam cotações -- As mensagens `instrument` chegam sempre primeiro; as cotações seguem-se assim que o handshake de dados de referência é concluído. Aguarde 10–20 segundos após a conexão antes de concluir que os dados estão em falta. +- As mensagens `instrument` chegam sempre primeiro; as cotações seguem quando o handshake de dados de referência completa. Aguarde 10–20 segundos após a conexão antes de concluir que os dados estão em falta. - Verifique `dz_feed_up{venue}` nas métricas — `0` significa que o multicast está silencioso no seu host. - Verifique se as regras de firewall permitem UDP multicast na interface `doublezero1`. ### `dz_ws_client_lagged_total` elevado -O seu consumidor está a ler mais lentamente do que a ponte está a publicar. Aumente o buffer de broadcast com `WS_BROADCAST_CAPACITY`, reduza o tempo de processamento por mensagem, ou adicione uma thread de leitura dedicada. +O seu consumidor está a ler mais devagar do que a ponte está a publicar. Aumente o buffer de broadcast com `WS_BROADCAST_CAPACITY`, reduza o tempo de processamento por mensagem, ou adicione uma thread de leitura dedicada. -### Contêiner termina imediatamente +### Container encerra imediatamente - A ponte requer `--network host` e o dispositivo `/dev/net/tun`; um `docker run` simples sem essas flags falhará. -- Use o comando de uma linha do instalador ou o comando exato `docker run` mostrado em [Self-hosting](#avancado-self-hosting). +- Use o comando de uma linha do instalador ou o comando `docker run` exato mostrado em [Self-hosting](#avancado-self-hosting). ### Túnel GRE não estabelece -Consulte [Resolução de Problemas](troubleshooting.md) e garanta que o protocolo IP 47 é permitido no seu provedor cloud. Na AWS, desative a verificação source/dest da ENI para o host. \ No newline at end of file +Consulte [Resolução de Problemas](troubleshooting.md) e assegure que o protocolo IP 47 é permitido no seu fornecedor de cloud. Na AWS, desative a verificação source/dest do ENI para o host. \ No newline at end of file diff --git a/docs/Edge Market Data Connection.zh.md b/docs/Edge Market Data Connection.zh.md index 087847a..c129ed1 100644 --- a/docs/Edge Market Data Connection.zh.md +++ b/docs/Edge Market Data Connection.zh.md @@ -1,17 +1,17 @@ --- -description: 运行 doublezero-edge-connect 将 Solana shred 重新转发到本地 UDP 端口,并通过本地 WebSocket 消费标准化的 Edge 市场数据。 +description: 运行 doublezero-edge-connect 将 Solana shreds 重新转发至本地 UDP 端口,并通过本地 WebSocket 消费标准化的 Edge 市场数据。 --- # Edge 连接 -!!! warning "连接到 DoubleZero 即表示我同意 [DoubleZero 使用条款](https://doublezero.xyz/terms-protocol)。数据仅供内部使用,不得转播(见第 2(e) 条)。" +!!! warning "连接到 DoubleZero 即表示我同意 [DoubleZero 使用条款](https://doublezero.xyz/terms-protocol)。数据仅供您内部使用,不得重新传输(参见第 2(e) 条)。" -`doublezero-edge-connect` 是一个桥接工具,它接入 **DoubleZero Edge 二进制组播**,并在本地以两种数据源的形式重新提供服务: +`doublezero-edge-connect` 是一个桥接工具,它加入 **DoubleZero Edge 二进制多播**,并在本地以两种数据源重新提供服务: -1. **Solana shred 转发** — 去重(可选签名验证)的 shred 分发到一个或多个本地 UDP 目的地,可直接供验证节点或 RPC 使用。 -2. **标准化市场数据** — Edge 交易所数据源经过解码、精度校正,并通过 `ws://host:8081` 上的单一 JSON WebSocket 重新提供服务。 +1. **Solana shred 转发** — 去重(可选签名验证)的 shreds 扇出到一个或多个本地 UDP 目标,可直接供您的验证器或 RPC 使用。 +2. **标准化市场数据** — Edge 交易所数据源经过解码、精度校正后,以单一 JSON WebSocket 在 `ws://host:8081` 上重新提供服务。 -两者运行在同一个容器中,使用相同的一行安装命令。根据您的链上授权启用所需的数据源。 +两者都从同一个容器运行,使用同一行安装命令。根据您的链上授权启用所需的数据源。 ``` ┌─ UDP datagrams ──▶ validator / RPC @@ -24,16 +24,16 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ ## 系统要求 -- **Linux/amd64** 主机,具有在链上已授权目标环境的公共 IPv4 地址。 -- **Docker**(一行安装命令会在缺失时自动安装)。 -- **GRE 连接** — 在云服务商处允许 IP 协议 47;在 AWS 上需禁用 ENI 源/目标检查。 -- **DoubleZero 访问密钥**:一个 `DZ_` 前缀的 base64 令牌或密钥对文件路径,通过 [DoubleZero 入驻流程](setup.md)获取。 +- **Linux/amd64** 主机,具有在链上为目标环境授权的公共 IPv4 地址。 +- **Docker**(一行命令安装脚本在缺失时会自动安装)。 +- **GRE 连通性** — 在您的云服务商处允许 IP 协议 47;在 AWS 上需禁用 ENI 的源/目标检查。 +- **DoubleZero 访问密钥**:一个 `DZ_` 前缀的 base64 令牌或密钥对文件路径,从 [DoubleZero 入门引导](setup.md) 流程获取。 --- ## 步骤 1:安装并运行 -一条命令即可准备主机并启动桥接容器。它会加入 DoubleZero 网络,并启动您的授权所涵盖的所有数据源 — shred 转发和/或 `:8081` 上的市场数据 WebSocket: +一条命令即可准备主机并启动桥接容器。它会加入 DoubleZero 网络,并启动您的授权所允许的所有数据源 — shred 转发和/或 `:8081` 上的市场数据 WebSocket: === "Mainnet-beta" @@ -54,87 +54,92 @@ DZ Edge multicast ──▶ doublezero-edge-connect ─┤ DZ_GHCR_TOKEN= curl -fsSL https://get.doublezero.xyz/connect-devnet | bash ``` -脚本执行内容: +脚本执行的操作: -1. 检查主机是否为 Linux/amd64,确认 Docker 已安装(缺失时提示安装)。 -2. 为 GRE 隧道准备主机内核:加载 `tun`/`ip_gre`,提高 `net.core.rmem_max`,提醒防火墙和云服务商规则。 -3. 加载您的访问密钥(如未设置 `DZ_SECRET`,会提示输入一次)。 +1. 检查主机是否为 Linux/amd64。 +2. 加载您的访问密钥(如未设置 `DZ_SECRET` 则提示输入一次),并**在安装任何内容之前在链上验证其访问通行证** — 这是一个纯主机端检查,通过账本的公共 JSON-RPC 进行。如果通行证绑定的 IP 与主机 IP 不同,当 IP 是通过 `DZ_CLIENT_IP` 显式指定时会中止;当 IP 仅是自动检测到的(NAT 后可能不准确)时会发出警告并继续,将真正的检查留给 `doublezero connect`。 +3. 确保 Docker 已安装(提供安装选项),并为 GRE 隧道准备主机内核:加载 `tun`/`ip_gre`,提高 `net.core.rmem_max`,提示防火墙和云服务商规则。 4. 运行桥接容器(`--network host`、`NET_ADMIN`/`NET_RAW`、`/dev/net/tun`)并执行 `doublezero connect multicast`。 !!! tip "非交互式安装" - 在管道命令前设置 `DZ_SECRET=DZ_…` 即可完全无人值守运行 — 无需任何确认提示。 + 在管道命令之前设置 `DZ_SECRET=DZ_…` 即可完全无人值守运行 — 不会有任何提示。 --- ## 步骤 2:配置 -所有配置均通过**管道命令前设置的环境变量**完成,没有配置文件。 +所有配置均通过**在管道命令之前设置环境变量**完成。没有配置文件。 ```bash DZ_SECRET=DZ_… VAR=value curl -fsSL https://get.doublezero.xyz/connect | bash ``` -### 安装程序变量 +### 安装器变量 | 变量 | 默认值 | 用途 | |------|--------|------| -| `DZ_SECRET` | *(交互式提示)* | `DZ_` 前缀的 base64 令牌**或**密钥对文件路径。令牌注入容器且不写入磁盘;文件以只读方式挂载。 | -| `DZ_ENV` | 取决于脚本 | `mainnet-beta` \| `testnet` \| `devnet`。 | -| `DZ_IMAGE` | 取决于脚本 | 覆盖容器镜像。 | +| `DZ_SECRET` | *(提示输入)* | `DZ_` 前缀的 base64 令牌**或**密钥对文件路径。令牌会注入容器且不会写入磁盘;文件以只读方式绑定挂载。 | +| `DZ_ENV` | 按脚本 | `mainnet-beta` \| `testnet` \| `devnet`。 | +| `DZ_IMAGE` | 按脚本 | 覆盖容器镜像。 | | `DZ_NAME` | `doublezero-edge-connect` | 容器名称。 | -| `DZ_FEEDS` | *(全部)* | 以逗号分隔的交易所列表,用于缩小市场数据摄入范围(例如 `VenueA,VenueB`)。不影响 Solana shred 转发。 | +| `DZ_FEEDS` | *(全部)* | 逗号分隔的交易所名称,用于缩小市场数据摄取范围(例如 `VenueA,VenueB`)。不影响 Solana shred 转发。 | +| `DZ_CLIENT_IP` | *(自动检测)* | 覆盖链上访问通行证预检查使用的公共 IPv4。当自动检测不准确时设置此项(例如在 NAT 后面),以便预检查能确认而非仅发出警告。 | +| `DZ_LEDGER_RPC_URL` | 按环境 | 覆盖预检查使用的 DoubleZero 账本 RPC 端点。 | | `DZ_ASSUME_YES` | `0` | 跳过确认提示(例如 Docker 安装提示)。 | | `DZ_GHCR_TOKEN` | — | **仅限 Devnet** — 具有 `read:packages` 权限的 GHCR 令牌(devnet 镜像为私有)。 | | `DZ_GHCR_USER` | `malbeclabs` | **仅限 Devnet** — 用于登录的 GHCR 用户名。 | ### 桥接变量 -安装程序会将**任何非空的**桥接变量直接传递到容器中。常用变量: +安装器会将**任何非空**的桥接变量直接传递到容器。常用变量: | 变量 | 默认值 | 用途 | |------|--------|------| | `DZ_IFACE` | `doublezero1` | 监听的网络接口。 | -| `DZ_RECV_BUF` | — | UDP 接收缓冲区覆盖值(字节)。 | +| `DZ_RECV_BUF` | `8388608` | UDP 接收缓冲区覆盖值(字节;默认 8 MiB)。 | | `METRICS_BIND` | *(空 / 关闭)* | 启用 Prometheus `/metrics` 端点(例如 `127.0.0.1:9090`)。 | -| `RUST_LOG` | `info` | 日志级别(`debug`、`warn` 等)。 | -| `DZ_SHRED_FORWARD` | — | 转发 shred 的本地 UDP 目的地 — 参见 [Solana Shred 转发](#solana-shred-转发)。 | -| `WS_BIND` | `0.0.0.0:8081` | 市场数据 WebSocket 绑定地址 — 参见[市场数据 WebSocket](#市场数据-websocket)。 | +| `RUST_LOG` | `warn,doublezero_edge_connect=info` | 日志级别(`debug`、`warn` 等)。 | +| `DZ_SHRED_FORWARD` | — | 转发 shreds 的本地 UDP 目标 — 参见 [Solana Shred 转发](#solana-shred-转发)。 | +| `WS_BIND` | `0.0.0.0:8081` | 市场数据 WebSocket 绑定地址 — 参见 [市场数据 WebSocket](#市场数据-websocket)。 | | `WS_MAX_CLIENTS` | `64` | 最大并发 WebSocket 客户端数。 | -| `WS_INPUT_COINS` | *(空 / 关闭)* | 为列出的交易对启用公共 WebSocket 后备源(例如 `BTC,ETH`)。 | +| `WS_INPUT_COINS` | *(空 / 关闭)* | 为已上线的交易对启用 Hyperliquid 公共 WebSocket 备用源(例如 `BTC,ETH`)— 参见 [输入源与 WebSocket 备用机制](#输入源与-websocket-备用机制)。 | +| `WS_INPUT_URL` | `wss://api.hyperliquid.xyz/ws` | 备用机制使用的 Hyperliquid 公共 WebSocket URL。 | +| `PHOENIX_WS_INPUT_MARKETS` | *(空 / 关闭)* | 为已上线的交易对启用 Phoenix 公共 WebSocket 备用源(仅交易数据)(例如 `SOL,BTC`)。 | +| `PHOENIX_WS_INPUT_URL` | `wss://perp-api.phoenix.trade/v1/ws` | 备用机制使用的 Phoenix 公共 WebSocket URL。 | **示例:** ```bash -# 将 shred 转发到本地验证节点/RPC: +# 将 shreds 转发到本地验证器/RPC: DZ_SECRET=DZ_… DZ_SHRED_FORWARD=127.0.0.1:20000 \ curl -fsSL https://get.doublezero.xyz/connect | bash # 非交互式,testnet: DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect-testnet | bash -# 缩小市场数据到特定交易所,详细日志,非默认 WS 端口: +# 缩小市场数据至特定交易所,启用详细日志,使用非默认 WS 端口: DZ_SECRET=DZ_… DZ_FEEDS=VenueA,VenueB RUST_LOG=debug WS_BIND=0.0.0.0:9000 \ curl -fsSL https://get.doublezero.xyz/connect | bash -# 启用指标和公共 WS 后备源: +# 启用指标和公共 WS 备用源: DZ_SECRET=DZ_… METRICS_BIND=127.0.0.1:9090 WS_INPUT_COINS=BTC,ETH \ curl -fsSL https://get.doublezero.xyz/connect | bash ``` !!! note - 由于安装程序只转发**非空**值,您无法通过一行安装命令传递空覆盖值(例如 `WS_BIND=""` 以禁用 WebSocket 输出)。此类场景请使用手写的 `docker run` — 参见[自托管](#高级自托管)。 + 安装器仅转发**非空**值,有一个例外:`WS_BIND` 即使设置为空也会被转发,因此 `WS_BIND=""` **确实会**通过一行命令禁用 WebSocket 输出。对于任何其他变量,空值覆盖无法通过管道传递 — 请使用手动编写的 `docker run`(参见 [自托管](#高级自托管))。 --- ## Solana Shred 转发 -桥接工具加入 `edge-solana-*` shred 组播组,并将每个数据报分发到一个或多个本地 UDP 目的地 — 直接从 Edge 网络为您的验证节点或 RPC 提供数据。当您的授权中存在这些组播组时,它会在发现时自动激活。 +桥接工具加入 `edge-solana-*` shred 多播组,并将每个数据报扇出到一个或多个本地 UDP 目标 — 使您的验证器或 RPC 直接从 Edge 网络获取数据。当这些组存在于您的授权中时,它会在发现时自动激活。 ```bash # 默认(仅去重,转发到本地端口 20000): DZ_SHRED_FORWARD=127.0.0.1:20000 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash -# 带签名验证: +# 启用签名验证: DZ_SHRED_DEDUP_MODE=sigverify \ DZ_SHRED_RPC_URL=https://api.mainnet-beta.solana.com \ DZ_SHRED_FORWARD=127.0.0.1:20000 \ @@ -143,28 +148,32 @@ DZ_SHRED_DEDUP_MODE=sigverify \ | 变量 | 默认值 | 用途 | |------|--------|------| -| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 转发 shred 的目的地(可重复设置)。 | -| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(每个 shred 一份副本)、`sigverify`(+ ed25519 验证)、`none`(所有数据报)。 | +| `DZ_SHRED_FORWARD` | `127.0.0.1:20000` | 转发 shreds 的目标地址(可重复设置)。 | +| `DZ_SHRED_DISABLE` | `0` | 主开关关闭(`--shred-forward-disable`)。无论您的授权允许什么都保持转发器关闭 — 当没有本地消费者在监听时设置此项,以避免将 shred 全量数据转发到无处而浪费 CPU。 | +| `DZ_SHRED_DEDUP_MODE` | `dedup` | `dedup`(每个 shred 一份)、`sigverify`(+ ed25519 验证)、`none`(所有数据报)。 | | `DZ_SHRED_RPC_URL` | — | Solana RPC 端点;`sigverify` 模式必需。 | | `DZ_SHRED_DEDUP_WINDOW_SLOTS` | `512` | 去重窗口大小。 | -详见 [Shred 转发](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md)了解完整管道和注意事项。 +参见 [Shred 转发](https://github.com/malbeclabs/doublezero-edge-connect/blob/main/docs/shred-forwarding.md) 了解完整管道和注意事项。 --- ## 市场数据 WebSocket -打开到 `ws://:8081` 的 WebSocket 连接并读取 JSON 帧。您将接收所有已授权交易所的数据。可通过可选的 `subscribe` 消息将流缩小到特定交易所和交易对。 +打开一个连接到 `ws://:8081` 的 WebSocket 并读取 JSON 帧。您会收到所有已授权交易所的数据。可选的 `subscribe` 消息可将数据流缩小到特定交易所和交易对。 -任何支持 WebSocket + JSON 的引擎只需一个轻量级(约 50–100 行)适配器即可消费数据。二进制组播、每个交易所的双端口拆分以及清单/精度握手都保留在桥接内部;消费者唯一需要对接的接口就是 WebSocket JSON。 +任何支持 WebSocket + JSON 的引擎都可以通过一个简单的(约 50–100 行)适配器来消费数据。二进制多播、每个交易所的双端口分离以及清单/精度握手都封装在桥接内部;消费者需要编码对接的唯一契约就是 WebSocket JSON。 + +!!! note + WebSocket 输出仅在您的授权中至少有一个市场数据源处于活跃状态时才会启动 — 仅有 shreds 的主机不提供 WebSocket。激活由链上订阅协调器驱动,每 30 秒刷新一次(`--subscription-refresh-secs`);`--subscription-gating-disable` 可选择退出此门控。 ### 连接生命周期 -每次新连接时,桥接会: +每个新连接建立时,桥接会: -1. **重放当前合约定义** — 每个已知交易对一条 `instrument` 消息 — 确保消费者在收到首个报价前已获得精度信息。 -2. **重放每个交易对的最新深度快照**(如果逐笔委托数据源处于活跃状态)。 -3. **流式推送** `quote` / `trade` / `midpoint` / `depth` 消息,到达后分发给所有已连接的消费者。 +1. **重放当前的品种定义** — 每个已知交易对一条 `instrument` 消息 — 使消费者在收到第一个报价之前就具备精度信息。 +2. **重放每个交易对的最新深度快照**(如果逐笔委托行情源处于活跃状态)。 +3. **流式推送** `quote` / `trade` / `midpoint` / `depth` 消息,扇出到所有已连接的消费者。 ``` connect → instrument (×N) → depth (×M, latest books) → quote → trade → depth → … @@ -172,16 +181,16 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade ### 消息类型 -每条消息都是一个带有 `type` 字段标签的 JSON 对象: +每条消息都是一个通过 `type` 字段标识的 JSON 对象: | `type` | 含义 | |--------|------| -| `instrument` | 合约/精度定义。 | -| `quote` | 最优买卖更新(完整状态)。 | -| `trade` | 成交记录(最新成交)。 | -| `midpoint` | 衍生中间价。 | +| `instrument` | 品种/精度定义。 | +| `quote` | 最优买卖价更新(完整状态)。 | +| `trade` | 成交记录(最近成交)。 | +| `midpoint` | 推导的中间价。 | | `depth` | 完整订单簿深度快照。 | -| `status` | 交易所级别的数据源健康状态变化。 | +| `status` | 交易所级别的数据源健康状态转换。 | 消费者**必须忽略未知的 `type` 值和未知字段**(前向兼容性)。 @@ -191,7 +200,7 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade {"type":"instrument","venue":"ExampleVenue","symbol":"SOL","price_exponent":-2,"qty_exponent":-2} ``` -在连接时发送,定义变更时也会发送。`price_exponent` 和 `qty_exponent` 以十的幂次表示交易所的最小价格变动和最小数量步长。 +在连接时发送,以及定义变更时发送。`price_exponent` 和 `qty_exponent` 以十的幂次表示交易所的最小价格变动和最小数量步长。 #### `quote` @@ -214,10 +223,10 @@ connect → instrument (×N) → depth (×M, latest books) → quote → trade ``` source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer recv) - venue book wire arrival post-decode WS hand-off + 交易所订单簿 网络到达 解码后 WS 移交 ``` -`0` 是"不可用"的哨兵值 — 将其视为缺失值,而非 1970 年。 +`0` 是"不可用"的哨兵值 — 将其视为缺失,而非 1970 年。 #### `trade` @@ -256,18 +265,18 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer {"type":"status","venue":"ExampleVenue","state":"down","stale_ms":30000,"ts_ns":...} ``` -当交易所的报价组播静默时发出(`state:"down"`),恢复时发出(`state:"ok"`)。用于在 UI 中将交易所置灰。报价推送不受状态限制 — 数据源在下一条报价时自动恢复。 +当交易所的报价多播静默时(`state:"down"`)或恢复时(`state:"ok"`)在边缘端发出。可用于在您的 UI 中将该交易所置灰。报价推送不受状态门控 — 数据源会在下一条报价时自动恢复。 ### 订阅 -默认情况下您会接收所有数据。发送控制消息以缩小流范围: +默认情况下您会收到所有数据。发送控制消息以缩小数据流: ```json {"method":"subscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} {"method":"unsubscribe","subscription":{"venue":"ExampleVenue","symbol":"SOL"}} ``` -省略某个字段将匹配任意值(`{"symbol":"SOL"}` = 所有交易所的 SOL)。`venue` 匹配不区分大小写。 +省略某个字段表示匹配任何值(`{"symbol":"SOL"}` = 所有交易所上的 SOL)。`venue` 匹配不区分大小写。 **服务器确认:** @@ -277,13 +286,13 @@ source_ts_ns → kernel_rx_ts_ns → recv_ts_ns → ws_send_ts_ns → (consumer 错误返回 `{"channel":"error","error":""}`。 -### 心跳与活性检测 +### 心跳和活跃性检测 -- 服务器每 20 秒发送一次 **WebSocket Ping**;合规客户端自动回复 Pong。 -- 60 秒无活动的客户端将被关闭并清理。 +- 服务器每 20 秒发送一个 **WebSocket Ping**;合规的客户端会自动回复 Pong。 +- 60 秒内无活动的客户端会被关闭并回收。 - 应用层保活:`{"method":"ping"}` → `{"channel":"pong"}`。 -### 消费者代码骨架 +### 消费者骨架代码 ```python import json, websocket @@ -304,29 +313,37 @@ def on_message(ws, frame): elif t == "depth": replace_book(msg["venue"], msg["symbol"], msg["bids"], msg["asks"]) - # unknown types: silently ignore (forward compatibility) + # 未知类型:静默忽略(前向兼容性) ws = websocket.WebSocketApp("ws://localhost:8081", on_message=on_message) ws.run_forever() ``` -### 输入源与 WebSocket 后备机制 +### 输入源与 WebSocket 备用机制 -Edge 组播数据源始终在线。可选的**公共 WebSocket 后备源**可在 Edge 数据源中断时填补缺口: +Edge 多播数据源始终在线。可选的**公共 WebSocket 备用源**可在 Edge 数据源停滞时填补缺口。目前提供两种备用源,默认均关闭,可按交易所独立启用: + +| 备用源 | 启用方式 | 覆盖范围 | 默认 URL | +|--------|----------|----------|----------| +| **Hyperliquid** | `WS_INPUT_COINS`(例如 `BTC,ETH`) | 报价 + 成交 | `wss://api.hyperliquid.xyz/ws`(`WS_INPUT_URL`) | +| **Phoenix** | `PHOENIX_WS_INPUT_MARKETS`(交易对代码,例如 `SOL,BTC`) | **仅成交** | `wss://perp-api.phoenix.trade/v1/ws`(`PHOENIX_WS_INPUT_URL`) | ```bash -# 为 BTC 和 ETH 启用后备源: +# 为 BTC 和 ETH 启用 Hyperliquid 备用源: WS_INPUT_COINS=BTC,ETH DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash + +# 为 SOL 启用 Phoenix 成交备用源: +PHOENIX_WS_INPUT_MARKETS=SOL DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash ``` -两个数据源在共享仲裁器内按 `(venue, symbol, source_ts)` 逐 tick 竞争。在稳态下 Edge 源获胜(亚毫秒 vs. 互联网上的数十毫秒);当 Edge 出现间隙时,公共副本进行填补。无论某次更新由哪个源送达,WebSocket 输出都是一致的。 +对于每个 `(venue, symbol, source_ts)` tick,Edge 和公共源在共享仲裁器中竞争。在稳定状态下 Edge 源胜出(亚毫秒级 vs. 互联网上的数十毫秒);当 Edge 出现间隙时,公共副本会填补。无论哪个源提供了给定更新,WebSocket 输出都是相同的。(Phoenix 备用源仅覆盖成交 — Edge 仍然是 Phoenix 报价的唯一来源。) --- ## 管理容器 ```bash -# 查看实时日志 +# 实时查看日志 sudo docker logs -f doublezero-edge-connect # 检查隧道状态 @@ -340,13 +357,13 @@ sudo docker stop doublezero-edge-connect && sudo docker rm doublezero-edge-conne ``` !!! note "无 TLS" - 桥接工具面向可信/本地网络。如果对外暴露 WebSocket 端点,请在反向代理处终止 TLS。 + 桥接工具面向可信/本地网络。如果您要将 WebSocket 端点暴露到外部,请在反向代理处终止 TLS。 --- ## 监控(Prometheus 指标) -指标端点**默认关闭**。使用 `METRICS_BIND` 启用: +指标端点**默认关闭**。通过 `METRICS_BIND` 启用: ```bash METRICS_BIND=127.0.0.1:9090 DZ_SECRET=DZ_… curl -fsSL https://get.doublezero.xyz/connect | bash @@ -360,17 +377,18 @@ curl -s localhost:9090/metrics | grep '^dz_' 关键指标: -| 指标 | 追踪内容 | +| 指标 | 跟踪内容 | |------|----------| -| `dz_feed_up{venue}` | 该交易所组播在线时为 `1`,静默时为 `0`。 | -| `dz_datagrams_received_total{venue}` | 每个交易所的摄入量。 | -| `dz_emit_total{venue,kind}` | 去重后按类型广播的消息数。 | -| `dz_quotes_dropped_total{venue}` | 被抑制的过期/重复报价数。 | -| `dz_ws_clients` | 当前已连接的 WebSocket 客户端数。 | +| `dz_feed_up{venue}` | 该交易所的多播在线时为 `1`,静默时为 `0`。 | +| `dz_datagrams_received_total{venue}` | 每个交易所的摄取量。 | +| `dz_emit_total{venue,kind}` | 去重后广播的消息数,按类型分类。 | +| `dz_quotes_admitted_total{venue,publisher}` | 仲裁器接受的报价数,归因于获胜来源。`publisher="public"` 上升表示备用源正在填补 Edge 间隙(稳定状态下为 `publisher="edge"`)。 | +| `dz_quotes_dropped_total{venue}` | 被抑制的过时/重复报价数。 | +| `dz_ws_clients` | 当前连接的 WebSocket 客户端数。 | | `dz_ws_messages_sent_total{kind}` | 转发给客户端的消息数。 | -| `dz_ws_client_lagged_total` | 为保护数据源而淘汰慢速客户端的次数。 | +| `dz_ws_client_lagged_total` | 为保护数据源而断开慢速客户端的次数。 | -同一绑定地址上还提供 `GET /healthz` 存活探针。 +同一绑定地址上还提供了 `GET /healthz` 活跃性探针。 --- @@ -384,7 +402,7 @@ curl -s localhost:9090/metrics | grep '^dz_' | Testnet | `ghcr.io/malbeclabs/doublezero-edge-connect` | `:testnet` | | Devnet(私有) | `ghcr.io/malbeclabs/doublezero-edge-connect-devnet` | `:latest` | -手动运行(适用于安装程序无法转发的选项,如 `WS_BIND=""`): +手动运行(适用于安装器无法转发的选项,例如 `WS_BIND=""`): ```bash docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ @@ -395,7 +413,7 @@ docker run --rm --network host --cap-add NET_ADMIN --device /dev/net/tun \ ghcr.io/malbeclabs/doublezero-edge-connect:mainnet-beta ``` -**从源码构建:** +**从源代码构建:** ```bash git clone https://github.com/malbeclabs/doublezero-edge-connect @@ -408,7 +426,7 @@ cargo test --ws-bind 0.0.0.0:8081 ``` -建议为突发数据源设置更大的内核接收缓冲区: +对于突发性数据源,建议增大内核接收缓冲区: ```bash sudo sysctl -w net.core.rmem_max=268435456 @@ -420,46 +438,24 @@ sudo sysctl -w net.core.rmem_max=268435456 | 限制 | 默认值 | 超出时的行为 | |------|--------|------------| -| 并发客户端数 (`WS_MAX_CLIENTS`) | 64 | 新连接被拒绝。 | -| 每个客户端的订阅数 (`WS_MAX_SUBS`) | 256 | `subscribe` 被拒绝并返回错误。 | -| 每客户端每分钟入站控制消息数 (`WS_MAX_INBOUND_PER_MIN`) | 600 | 客户端被断开连接。 | -| 广播缓冲区 (`WS_BROADCAST_CAPACITY`) | 4096 | 慢速客户端**丢弃最旧的消息**(永远不会阻塞数据源)。 | +| 并发客户端数(`WS_MAX_CLIENTS`) | 64 | 新连接被拒绝。 | +| 每客户端订阅数(`WS_MAX_SUBS`) | 256 | `subscribe` 请求被拒绝并返回错误。 | +| 每客户端每分钟入站控制消息数(`WS_MAX_INBOUND_PER_MIN`) | 600 | 客户端被断开连接。 | +| 广播缓冲区(`WS_BROADCAST_CAPACITY`) | 4096 | 慢速客户端**丢弃最旧的消息**(绝不会阻塞数据源)。 | -由于每条 `quote` 和 `depth` 都是完整状态,消费者在背压下丢失消息后会在下一条消息时自动恢复 — 无需重新同步握手。 +由于每条 `quote` 和 `depth` 都是完整状态,在背压下丢失消息的消费者会在下一条消息时自动恢复 — 无需重新同步握手。 --- -## 故障排除 - -### 本地端口未收到 shred - -- 确认您的访问已在链上获得 `edge-solana-*` shred 组的授权。 -- 验证隧道是否正常:`sudo docker exec -it doublezero-edge-connect doublezero status` -- 检查日志中的加入错误:`sudo docker logs -f doublezero-edge-connect` -- 确认 `DZ_SHRED_FORWARD` 指向一个可达的本地 UDP 目的地。 +## 故障排查 -### 未收到某个交易所的消息 +### 本地端口没有收到 shreds -- 验证隧道是否正常:`sudo docker exec -it doublezero-edge-connect doublezero status` +- 确认您的访问已在链上为 `edge-solana-*` shred 组授权。 +- 验证隧道是否建立:`sudo docker exec -it doublezero-edge-connect doublezero status` - 检查日志中的加入错误:`sudo docker logs -f doublezero-edge-connect` -- 确认您的访问已在链上获得该交易所的授权。 -- 使用 `DZ_FEEDS=` 缩小摄入范围以隔离问题。 - -### WebSocket 已连接但未收到报价 - -- `instrument` 消息始终先到达;报价在参考数据握手完成后才会跟进。连接后请等待 10–20 秒再判断数据是否缺失。 -- 检查指标中的 `dz_feed_up{venue}` — `0` 表示组播在您的主机上处于静默状态。 -- 验证防火墙规则是否允许 `doublezero1` 接口上的组播 UDP。 - -### `dz_ws_client_lagged_total` 值过高 - -您的消费者读取速度慢于桥接发布速度。请使用 `WS_BROADCAST_CAPACITY` 增大广播缓冲区,减少每条消息的处理时间,或添加专用读取线程。 - -### 容器立即退出 - -- 桥接工具需要 `--network host` 和 `/dev/net/tun` 设备;没有这些标志的普通 `docker run` 会失败。 -- 请使用一行安装命令或[自托管](#高级自托管)中展示的完整 `docker run` 命令。 +- 确认 `DZ_SHRED_FORWARD` 指向可达的本地 UDP 目标。 -### GRE 隧道无法建立 +### 没有收到某个交易所的消息 -请参阅[故障排除](troubleshooting.md),并确保在云服务商处已允许 IP 协议 47。在 AWS 上,需为主机禁用 ENI 源/目标检查。 \ No newline at end of file +- 验证隧道 \ No newline at end of file