Skip to content

Commit c95d4bc

Browse files
committed
resolver: dockerize
1 parent 40e6510 commit c95d4bc

5 files changed

Lines changed: 149 additions & 8 deletions

File tree

scripts/resolver/Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# syntax=docker/dockerfile:1.7
2+
# ---------- builder ----------
3+
# Use the official uv image (Astral) on top of a slim Python base.
4+
# uv resolves and installs the lockfile-free pyproject.toml in seconds and
5+
# produces a portable .venv we can copy into the runtime stage.
6+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder
7+
8+
ENV UV_LINK_MODE=copy \
9+
UV_COMPILE_BYTECODE=1 \
10+
UV_PYTHON_DOWNLOADS=never \
11+
UV_NO_PROGRESS=1
12+
13+
WORKDIR /app
14+
15+
# Install deps first (separate layer) — script edits won't bust this cache.
16+
COPY pyproject.toml ./
17+
RUN --mount=type=cache,target=/root/.cache/uv \
18+
uv sync --no-dev --no-install-project
19+
20+
# Script is added after the dep layer for cache friendliness.
21+
COPY snrc-resolve.py ./
22+
23+
# ---------- runtime ----------
24+
# Slim runtime — only the venv + script. No uv, no apt.
25+
FROM python:3.13-slim AS runtime
26+
27+
ENV PYTHONUNBUFFERED=1 \
28+
PYTHONDONTWRITEBYTECODE=1 \
29+
PATH="/app/.venv/bin:$PATH"
30+
31+
# Non-root user (matches resolver privacy posture: it has no need for root).
32+
RUN groupadd --system --gid 10001 snrc && \
33+
useradd --system --uid 10001 --gid snrc --no-create-home --shell /usr/sbin/nologin snrc
34+
35+
WORKDIR /app
36+
COPY --from=builder --chown=snrc:snrc /app /app
37+
38+
USER snrc:snrc
39+
40+
EXPOSE 8000
41+
42+
# Liveness check hits the script's own /health route. ThreadingHTTPServer is
43+
# fast enough that 3s is generous for a localhost probe; restart if it stops
44+
# responding entirely.
45+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
46+
CMD ["python", "-c", "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).status == 200 else 1)"]
47+
48+
ENTRYPOINT ["python", "snrc-resolve.py"]

scripts/resolver/README.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,21 @@ Registry (SNRC) over a small JSON HTTP API. It talks to the same local
5252
Reth + Nimbus stack described above (set `NETWORK=mainnet` in `.env`),
5353
reading the SNRC contracts directly on Ethereum mainnet.
5454

55-
Install the only runtime dependency (same as `ens-lookup.py`):
55+
Dependencies are declared inline (PEP 723) at the top of `snrc-resolve.py`
56+
and in a sibling `pyproject.toml`. The simplest local run uses
57+
[`uv`](https://docs.astral.sh/uv/):
5658

5759
```sh
58-
pip install --break-system-packages 'eth-hash[pycryptodome]'
60+
uv run scripts/resolver/snrc-resolve.py
61+
```
62+
63+
`uv` resolves and caches `eth-hash[pycryptodome]` on first run. No
64+
virtualenv juggling, no `--break-system-packages`. If you'd rather
65+
manage Python deps yourself:
66+
67+
```sh
68+
pip install 'eth-hash[pycryptodome]>=0.7'
69+
python scripts/resolver/snrc-resolve.py
5970
```
6071

6172
### Deployed registries
@@ -93,6 +104,38 @@ snrc-resolve listening on 0.0.0.0:8000
93104

94105
Override the listen port or bind address with `SNRC_PORT` / `SNRC_BIND`.
95106

107+
### Running in Docker
108+
109+
The compose file ships a `resolver` service alongside reth and nimbus.
110+
`docker compose up -d` builds the image from `Dockerfile` (multi-stage,
111+
non-root, `uv`-based) and exposes the API on `127.0.0.1:8000`:
112+
113+
```sh
114+
docker compose up -d resolver
115+
docker compose logs -f resolver
116+
curl -s http://127.0.0.1:8000/health
117+
```
118+
119+
The container points `SNRC_RPC` at `http://reth:8545` (the compose-internal
120+
DNS name) so the resolver and reth share the bridge network without
121+
exposing reth's RPC to the host beyond loopback.
122+
123+
To change the host-side port, edit the LEFT side of the port mapping in
124+
`docker-compose.yml`:
125+
126+
```yaml
127+
resolver:
128+
ports:
129+
- "127.0.0.1:8000:8000" # host:container
130+
```
131+
132+
The registry address defaults to mainnet `.testing` — to override (Holesky,
133+
a private deployment, or future `.simplex`), uncomment and set the values
134+
in `docker-compose.yml` under the resolver service's `environment:` block.
135+
136+
The image declares a `HEALTHCHECK` against `/health`; `docker compose ps`
137+
will mark the service `(healthy)` once reth is queryable.
138+
96139
### Resolving a name
97140

98141
`foobar.testing` is registered on mainnet with every text and

scripts/resolver/docker-compose.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,34 @@ services:
127127
--nat=${NAT:-any}
128128
restart: unless-stopped
129129

130+
# SNRC REST resolver. Talks to reth on the compose-internal network,
131+
# exposes /resolve and /health on 127.0.0.1:8000 by default. The
132+
# smp-server points its [NAMES] resolver_endpoint at this URL.
133+
# To change the host port, edit the LEFT side of the port mapping below.
134+
resolver:
135+
build:
136+
context: .
137+
dockerfile: Dockerfile
138+
depends_on:
139+
# reth's `service_started` is sufficient — the resolver tolerates
140+
# eth_call failures gracefully (returns 502 with the error body), so
141+
# starting before reth has finished snapshot replay just yields a few
142+
# 502s until the chain is queryable. The upstream reth image doesn't
143+
# ship a HEALTHCHECK, so we can't gate on healthy.
144+
reth:
145+
condition: service_started
146+
environment:
147+
SNRC_RPC: http://reth:8545
148+
SNRC_BIND: 0.0.0.0
149+
# Registry addresses cascade through the script's own defaults
150+
# (mainnet `.testing`; `.simplex` unconfigured). Set explicitly here
151+
# only if you're deploying against a different network or contract.
152+
# SNRC_REGISTRY_TESTING: 0x...
153+
# SNRC_REGISTRY_SIMPLEX: 0x...
154+
ports:
155+
- "127.0.0.1:8000:8000"
156+
restart: unless-stopped
157+
130158
volumes:
131159
reth-data:
132160
nimbus-data:

scripts/resolver/pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "snrc-resolve"
3+
version = "0.1.0"
4+
description = "SimpleX Namespace (SNRC) resolver — REST API over ENS-shaped Ethereum registries"
5+
readme = "README.md"
6+
requires-python = ">=3.11"
7+
license = "AGPL-3.0-only"
8+
dependencies = [
9+
"eth-hash[pycryptodome]>=0.7",
10+
]
11+
12+
[tool.uv]
13+
package = false

scripts/resolver/snrc-resolve.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
#!/usr/bin/env python3
2+
# /// script
3+
# requires-python = ">=3.11"
4+
# dependencies = [
5+
# "eth-hash[pycryptodome]>=0.7",
6+
# ]
7+
# ///
28
"""SimpleX Namespace (SNRC) resolver — REST API.
39
410
Resolves names like `alice.testing` / `bob.simplex` against the SNRC
@@ -33,8 +39,9 @@
3339
Each TLD is a separate SNRC deployment with its own ENSRegistry; the
3440
resolver dispatches by the queried name's rightmost label.
3541
36-
Same dependency surface as ens-lookup.py:
37-
pip install --break-system-packages 'eth-hash[pycryptodome]'
42+
Dependencies are declared inline (PEP 723) at the top of this file. Run with:
43+
uv run snrc-resolve.py # uv resolves & caches deps; one-line setup
44+
python snrc-resolve.py # if eth-hash[pycryptodome] is already installed
3845
3946
Addresses are returned in each chain's canonical presentation:
4047
eth EIP-55 mixed-case checksummed hex (e.g. 0xEa65A0…1572)
@@ -62,11 +69,13 @@
6269
# Each TLD is its own SNRC deployment with its own ENSRegistry. Dispatch
6370
# happens on the rightmost label of the queried name. Empty / unset means
6471
# "not deployed" — requests for that TLD return 400 with a clear error.
72+
# `... or "..."` makes the script's defaults the single source of truth:
73+
# unset AND empty-string both fall through to the literal. docker-compose
74+
# can therefore pass `SNRC_REGISTRY_TESTING=${SNRC_REGISTRY_TESTING:-}`
75+
# without duplicating the registry address.
6576
REGISTRIES = {
66-
"testing": os.environ.get(
67-
"SNRC_REGISTRY_TESTING",
68-
"0x03f438da0bd44da3c6c1d0392f8ba183b8b3a7a6", # mainnet .testing
69-
),
77+
"testing": os.environ.get("SNRC_REGISTRY_TESTING", "")
78+
or "0x03f438da0bd44da3c6c1d0392f8ba183b8b3a7a6", # mainnet .testing
7079
"simplex": os.environ.get("SNRC_REGISTRY_SIMPLEX", ""), # not deployed yet
7180
}
7281

0 commit comments

Comments
 (0)