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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/scripts/tests/test_validate_testnet_bens.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ def setUp(self):
def test_repository_configuration_is_valid(self):
self.assertEqual([], self.module.validate())

def test_adapter_manifest_mount_is_outside_read_only_code_directory(self):
compose = (ROOT / "docker-compose" / "docker-compose-testnet.yml").read_text(
encoding="utf-8"
)
match = re.search(
r"^ bens-ensv2-adapter:.*?(?=^ [a-zA-Z0-9_-]+:|\Z)",
compose,
re.DOTALL | re.MULTILINE,
)
self.assertIsNotNone(match, "Missing Testnet adapter service")
adapter = match.group(0)
self.assertIn("./bens/subgraph/adapter:/app:ro", adapter)
self.assertIn("./bens/deployment.json:/deployment.json:ro", adapter)
self.assertIn("DOS_NAMES_DEPLOYMENT: /deployment.json", adapter)
self.assertNotIn(":/app/deployment.json:ro", adapter)

def test_non_root_adapter_artifacts_have_explicit_readable_modes(self):
workflow = (ROOT / ".github" / "workflows" / "deploy-config.yml").read_text(
encoding="utf-8"
)
self.assertIn('sudo chmod 0755 "${DEPLOY_PATH}/bens/subgraph/adapter"', workflow)
self.assertIn('sudo chmod 0644 "${DEPLOY_PATH}/bens/subgraph/adapter/server.mjs"', workflow)
self.assertIn('"${DEPLOY_PATH}/bens/deployment.json"', workflow)

def test_validator_rejects_public_http_routing_for_internal_bens_history(self):
template = json.loads(self.module.BENS_TEMPLATE.read_text(encoding="utf-8"))
template["subgraphs_reader"]["networks"]["3939"]["blockscout"]["url"] = (
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/dependency-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,33 @@ jobs:
node --test "${checkout}/subgraph/dos-names/adapter/server.test.mjs"
cp "${checkout}/contracts/deployments/dos-testnet-3939.json" \
docker-compose/bens/deployment.json
# Exercise real read-only mounts and the production entrypoint, not just module tests.
chmod 0755 "${checkout}/subgraph/dos-names/adapter"
chmod 0644 "${checkout}/subgraph/dos-names/adapter/server.mjs" \
docker-compose/bens/deployment.json
docker run --rm --init --network none --user 1000:1000 --read-only \
--cap-drop ALL --security-opt no-new-privileges:true --memory 128m --cpus 0.5 \
--platform linux/amd64 \
--mount "type=bind,src=${checkout}/subgraph/dos-names/adapter,dst=/app,readonly" \
--mount "type=bind,src=${PWD}/docker-compose/bens/deployment.json,dst=/deployment.json,readonly" \
--env DOS_NAMES_DEPLOYMENT=/deployment.json \
node:22.18.0-alpine3.22@sha256:1b2479dd35a99687d6638f5976fd235e26c5b37e8122f786fcd5fe231d63de5b \
node -e '
const { spawn } = require("node:child_process");
const child = spawn(process.execPath, ["/app/server.mjs"], { stdio: ["ignore", "pipe", "inherit"] });
let output = "";
let started = false;
const deadline = setTimeout(() => child.kill("SIGKILL"), 5000);
child.stdout.on("data", (chunk) => {
output += chunk;
if (output.includes("DOS Names Testnet metadata adapter listening")) {
started = true;
child.kill("SIGTERM");
}
});
child.on("error", () => { process.exitCode = 1; });
child.on("close", () => { clearTimeout(deadline); process.exitCode = started ? 0 : 1; });
'
node "${checkout}/subgraph/dos-names/scripts/render-manifest.mjs" \
docker-compose/bens/deployment.json \
"${checkout}/subgraph/dos-names/subgraph.template.yaml" \
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,9 @@ jobs:
sudo cp -a "${SRC}/docker-compose/bens" "${DEPLOY_PATH}/bens"
sudo chmod 0755 "${DEPLOY_PATH}/bens"
sudo chmod 0644 "${DEPLOY_PATH}/bens/config.json"
sudo chmod 0755 "${DEPLOY_PATH}/bens/subgraph/adapter"
sudo chmod 0644 "${DEPLOY_PATH}/bens/subgraph/adapter/server.mjs" \
"${DEPLOY_PATH}/bens/deployment.json"

cd "${DEPLOY_PATH}"
sudo env DOSCAN_BLOCKSCOUT_SECRETS_ENV="${SECRETS_ENV}" docker compose config -q
Expand Down
4 changes: 3 additions & 1 deletion docker-compose/docker-compose-testnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ services:
- no-new-privileges:true
mem_limit: 128m
cpus: 0.5
environment:
DOS_NAMES_DEPLOYMENT: /deployment.json
depends_on:
bens:
condition: service_started
bens-graph-node:
condition: service_started
volumes:
- ./bens/subgraph/adapter:/app:ro
- ./bens/deployment.json:/app/deployment.json:ro
- ./bens/deployment.json:/deployment.json:ro
command: ["node", "/app/server.mjs"]
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8050/health').then(r=>r.json()).then(r=>process.exit(r.status==='SERVING'?0:1)).catch(()=>process.exit(1))"]
Expand Down
2 changes: 2 additions & 0 deletions docs/superpowers/plans/2026-08-12-ensv2-bens-testnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
- Source adapter accepted: DOS Names PR #11 merged as `2d47fb77bb09dcbdc9d7bfeee77d12ddbebc2e4d`, with final-head independent review and successful required CI. Adapter tests cover canonical/retired tokens, subdomains, regeneration, invalid state and client-disconnect cancellation.
- Testnet fixture renewal accepted: `0xce800d5b23a7eb1a0cf7318585af76b02e0e93e0b7ce468038ff941732c1094c` succeeded at block 299. `policy-smoke.dos` expiry changed from 1824131273 to 1855667273 (+365 days); token ID and owner were unchanged. Positive payment transfer, paidExpiry and post-renewal nonce replay rejection were verified. BENS history includes the claim and renewal receipts, and its expiry is `2028-10-20T15:07:53Z`.
- Negative policy simulations passed for WrongWallet, InvalidVoucher, VoucherExpired, PriceExceedsVoucher, NonceAlreadyUsed and UnauthorizedRegistrationController. Only existing Testnet fixture keys were used in memory; no keys/signatures were logged or persisted. These checks do not prove DOS.Me authentication/score admission for a real user.
- First runtime attempt: DOScan PR #273 merged as `b1686490b0a454c2b66d2d5cb9ffb9a9cee4a99d`. Deploy Config run `40197157966` failed because Docker could not create `/app/deployment.json` inside the read-only `/app` code bind mount. The rollback stopped at its database-writer container-state gate before any database drop/restore. Prior public configuration and services were recovered using the existing persistent volumes; Backend became healthy, BENS returned SERVING, and the public explorer read block 300. No database restore or Mainnet action was performed.
- Runtime correction: mount the manifest at `/deployment.json`, outside the read-only code directory, and select it with `DOS_NAMES_DEPLOYMENT`. The text regression enforces that corrected layout; the separate Docker CI probe verifies actual entrypoint startup. VM preflight also found public artifact modes of 0700/0600. Explicitly setting only the adapter directory to 0755 and its entrypoint/manifest to 0644 allowed the production entrypoint to start on the VM with UID 1000, read-only mounts/rootfs, dropped capabilities, resource limits and no network. Secret modes are unchanged. The separate container-removal diagnostic remains a follow-up; its safety gate is unchanged.

---

Expand Down
3 changes: 2 additions & 1 deletion scripts/validate-testnet-bens.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ def validate() -> list[str]:
if (
" bens-ensv2-adapter:" not in compose
or "./bens/subgraph/adapter:/app:ro" not in compose
or "./bens/deployment.json:/app/deployment.json:ro" not in compose
or "./bens/deployment.json:/deployment.json:ro" not in compose
or "DOS_NAMES_DEPLOYMENT: /deployment.json" not in compose
):
errors.append("Testnet must run the immutable DOS Names metadata adapter")

Expand Down
Loading