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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ RUN if [ "$PRUNE_MODULES" = "true" ]; then \
# Stage 2: runtime
# Minimal image with only what the node needs at run time. Runs as non-root.
# =============================================================================
# ── wstcp (TLSNotary websocket↔TCP proxy) ─────────────────────────────
# The TLSNotary flow spawns `wstcp` on demand to relay the browser's
# WebSocket to the target TLS server (see proxyManager.ts). The runtime
# image ships no Rust toolchain, so the on-demand `cargo install wstcp`
# fallback in ensureWstcp() cannot run — bake the binary in instead.
# Without it the proxy never binds its port and every verification fails
# with an nginx 502 / CloseEvent 1006 on the prover.
FROM rust:1-slim AS wstcp
# Pin the version so an upstream wstcp release can't silently change
# behaviour or break the build. `--locked` is intentionally omitted: the
# crate's bundled Cargo.lock pins dependency versions that no longer
# compile on the current toolchain, so we let cargo resolve compatible
# deps for this exact wstcp version.
RUN cargo install wstcp --version 0.2.1 --root /wstcp

FROM oven/bun:1.3-debian AS runtime

# OCI image metadata.
Expand Down Expand Up @@ -178,6 +193,11 @@ RUN chmod 0755 /app/scripts/docker-entrypoint.sh \
&& chown demos:demos /app /app/data /app/logs /app/state \
&& chmod 0755 /app /app/data /app/logs /app/state

# TLSNotary proxy binary, baked in so the on-demand proxy can spawn
# without a Rust toolchain. Lands at $HOME/.cargo/bin/wstcp (HOME=/app) —
# the exact path proxyManager.ts::ensureWstcp() probes via `test -x`.
COPY --from=wstcp --chown=demos:demos /wstcp/bin/wstcp /app/.cargo/bin/wstcp

# Build-time provenance. These ARGs are populated by the build driver
# (compose passes `git rev-parse HEAD` + `git rev-parse --abbrev-ref HEAD`
# + `git diff --quiet; echo $?` + an ISO timestamp). They land in the
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.devnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ services:
container_name: demos-node-devnet
environment:
TLSNOTARY_PORT: 7147
# wstcp proxy window, offset +100 from mainnet (55000-55063) so the
# two stacks don't fight over the same host ports. The allocation
# range and the published range below read these same values.
TLSNOTARY_PROXY_PORT_MIN: 55100
TLSNOTARY_PROXY_PORT_MAX: 55163
networks: !override
- demos-network-devnet
# node_data/node_logs/node_state are isolated by the top-level `name:`
Expand All @@ -69,6 +74,11 @@ services:
- "53651:${OMNI_PORT:-53551}"
- "3105:${RPC_SIGNALING_PORT:-3005}"
- "9190:9090"
# wstcp TLSNotary proxy range (devnet window), localhost-bound so only
# the host reverse proxy reaches it. MUST match TLSNOTARY_PROXY_PORT_MIN/
# MAX above. Host:container are equal (no offset) — the node advertises
# the container-internal port and nginx forwards /tlsn/<port>/ to it.
- "127.0.0.1:55100-55163:55100-55163"

prometheus:
container_name: demos-prometheus-devnet
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ services:
TLSNOTARY_MODE: ${TLSNOTARY_MODE:-docker}
TLSNOTARY_FATAL: ${TLSNOTARY_FATAL:-false}
TLSNOTARY_SIGNING_KEY: ${TLSNOTARY_SIGNING_KEY:-}
# wstcp proxy port window. The node spawns dynamic wstcp proxies in
# this range and the host reverse proxy forwards /tlsn/<port>/ to them,
# so the SAME range must be host-published in `ports:` below — kept
# narrow to avoid a 2000-port mapping. Both stanzas read these vars so
# the allocation range and the published range can never drift.
TLSNOTARY_PROXY_PORT_MIN: ${TLSNOTARY_PROXY_PORT_MIN:-55000}
TLSNOTARY_PROXY_PORT_MAX: ${TLSNOTARY_PROXY_PORT_MAX:-55063}
# Logging & misc
LOG_LEVEL: ${LOG_LEVEL:-info}
PROD: ${PROD:-false}
Expand All @@ -198,6 +205,12 @@ services:
- "${RPC_PORT:-53550}:${RPC_PORT:-53550}"
- "${OMNI_PORT:-53551}:${OMNI_PORT:-53551}"
- "${RPC_SIGNALING_PORT:-3005}:${RPC_SIGNALING_PORT:-3005}"
# wstcp TLSNotary proxy range, bound to 127.0.0.1 so ONLY the host's
# reverse proxy can reach it (not the public internet). The node
# allocates dynamic proxy ports here and nginx forwards
# /tlsn/<port>/ to them — without this mapping every verification
# 502s. MUST match TLSNOTARY_PROXY_PORT_MIN/MAX in `environment:`.
- "127.0.0.1:${TLSNOTARY_PROXY_PORT_MIN:-55000}-${TLSNOTARY_PROXY_PORT_MAX:-55063}:${TLSNOTARY_PROXY_PORT_MIN:-55000}-${TLSNOTARY_PROXY_PORT_MAX:-55063}"
# MCP (Model Context Protocol) intentionally NOT host-published.
# The server binds `localhost` inside the container (src/index.ts)
# and the SDK has no built-in authentication — publishing this port
Expand Down
14 changes: 10 additions & 4 deletions src/features/tlsnotary/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ export const SIGNING_KEY_FILE_MODE = 0o600
* Configuration constants for port allocation and proxy lifecycle
*/
export const PORT_CONFIG = {
/** Minimum port number in the allocation range */
PORT_MIN: 55000,
/** Maximum port number in the allocation range */
PORT_MAX: 57000,
/**
* Minimum/maximum port for wstcp proxy allocation. Overridable via env so
* the published host range (docker-compose `ports:`) can be narrowed to a
* window the reverse proxy can actually reach — the proxies bind dynamic
* ports in this range and nginx forwards `/tlsn/<port>/` to them, so the
* range MUST be host-reachable or every verification 502s. Defaults keep
* the historical 55000-57000 behaviour.
*/
PORT_MIN: Number(process.env.TLSNOTARY_PROXY_PORT_MIN) || 55000,
PORT_MAX: Number(process.env.TLSNOTARY_PROXY_PORT_MAX) || 57000,
Comment thread
Shitikyan marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/** Idle timeout before a proxy is considered stale (30 seconds) */
IDLE_TIMEOUT_MS: 30000,
/** Maximum number of spawn retry attempts */
Expand Down
15 changes: 4 additions & 11 deletions src/features/tlsnotary/portAllocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@
// REVIEW: TLSNotary port pool management for wstcp proxy instances
import * as net from "net"
import log from "@/utilities/logger"

/**
* Configuration constants for port allocation
*/
export const PORT_CONFIG = {
PORT_MIN: 55000,
PORT_MAX: 57000,
IDLE_TIMEOUT_MS: 30000, // 30 seconds
MAX_SPAWN_RETRIES: 3,
SPAWN_TIMEOUT_MS: 5000, // 5 seconds to wait for wstcp to start
}
// Single source of truth for the proxy port window, so the env-overridable
// range (TLSNOTARY_PROXY_PORT_MIN/MAX) actually drives allocation — a local
// hardcoded copy here would silently ignore the published host range.
import { PORT_CONFIG } from "./constants"

/**
* Port pool state interface
Expand Down
2 changes: 1 addition & 1 deletion src/features/tlsnotary/proxyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ import { promisify } from "util"
import log from "@/utilities/logger"
import { getSharedState } from "@/utilities/sharedState"
import {
PORT_CONFIG,
initPortPool,
allocatePort,
releasePort,
type PortPoolState,
} from "./portAllocator"
import { PORT_CONFIG } from "./constants"

const execAsync = promisify(exec)

Expand Down
Loading