Skip to content

Prebuilt dot.li bundles with runtime network overrides - #156

Open
mordamax wants to merge 6 commits into
mainfrom
feat/prebuilt-runtime-configurable-bundles
Open

Prebuilt dot.li bundles with runtime network overrides#156
mordamax wants to merge 6 commits into
mainfrom
feat/prebuilt-runtime-configurable-bundles

Conversation

@mordamax

Copy link
Copy Markdown
Contributor

Summary

Publishes two prebuilt dot.li artifacts per release, both configurable at run time so one artifact works against any chain:

Artifact Server Needs
ghcr.io/paritytech/dotli-community:<version> nginx Docker
dotli-<version>.tar.gz on the release serve.mjs node ≥ 22 or bun
docker run -p 5173:5173 -e DOTLI_NETWORK='{…}' ghcr.io/paritytech/dotli-community:0.7.4

tar xzf dotli-0.7.4.tar.gz && cd dotli-0.7.4
DOTLI_NETWORK='{…}' node serve.mjs      # → http://browse.localhost:5173

Both read the same $DOTLI_NETWORK, so a fork config is portable between them.

Addresses paritytech/testenv-manager#18 (tarball for native, docker for k8s, runtime overrides rather than build-time). Cross-repo keywords don't auto-close, so that issue needs closing by hand.

Runtime overrides

JSON patching the built-in network tables, so only the differences are stated:

{
  "enabled": ["previewnet"],
  "networks": {
    "previewnet": {
      "label": "My fork",
      "assethub": { "rpcs": ["ws://127.0.0.1:10020"] },
      "bulletin": { "rpcs": ["ws://127.0.0.1:10030"],
                    "ipfsGateways": ["http://127.0.0.1:8080"] }
    }
  }
}

Endpoints onlylabel, rpcs, ipfsGateways. Never genesis or dotns: those are the trust root for name resolution, and an override that could repoint the DotNS registry would let anything running in the page redirect every .dot lookup while isVerifiedSession() still reported "verified". Limiting the surface to endpoints means the worst an override can do is move you to a different node for the same chain identity, which the light client verifies against the compiled-in genesis anyway.

That limit also keeps the change small: the protocol SharedWorker reads only genesis and dotns, so it needs no runtime config at all — no async hydration, no boot-ordering, and the network table is still built once at module init.

Other deliberate constraints: patches existing networks only (so NetworkName stays a closed union — use label to rename), and arrays replace rather than concatenate, because appending would leave a fork's endpoint pooled with the public ones and produce intermittent, hard-to-diagnose behaviour.

Anything unrecognised — unknown network, misspelled field, wrong type, __proto__ — throws instead of being skipped. A silently ignored override means running against the public chain while believing otherwise, which is the failure this exists to prevent.

Off by default outside the prebuilt artifacts

Gated behind VITE_RUNTIME_NETWORK_CONFIG, set only by the Dockerfile and the release workflow. A stable, documented hook into network configuration that survives every release is cheaper to abuse than patching a hash-named bundle, and the hosted deployments have no use for it. Both halves are gated independently, so neither alone enables it:

  • the vite plugin injects the config script on opt-in rather than stripping it on opt-out, so a default build's HTML is byte-identical to one from before this existed;
  • RUNTIME_CONFIG_ENABLED gates the reader — in a default build the minifier folds the guard and readRuntimeConfig() compiles to return null, so the global read is absent rather than merely bypassed.

baseDomain

dot.li derives its base domain from the last two hostname segments, which is wrong for anything deeper — dotli.ppn-65iw.pdp-stg-scw.parity.io derives parity.io, then looks for its protocol iframe at host.parity.io. Such hosts can now state it explicitly. Validated as at least two segments and a suffix of the actual hostname, so a page can't declare an unrelated base domain and widen the cross-origin allowlist.

This is what makes the image usable behind an ingress; frame-ancestors is derived from DOMAIN for the same reason.

Serving rules, in three places now

nginx.conf.template (deployed), nginx.docker.conf.template (container) and scripts/serve.ts (tarball) all implement the same hostname routing and headers. Nothing enforces agreement, and drift would mean a fork is tested under different headers than production — the sandbox isolation leans on frame-ancestors and COEP. Cross-referenced in comments; a parity check would be the honest follow-up.

scripts/preview-server.ts is untouched apart from serving the config path, since two Playwright configs depend on its behaviour.

Notes for review

  • The two build settings are load-bearing and asserted, not trusted. Without VITE_RUNTIME_NETWORK_CONFIG the script tag isn't emitted and the served config is silently ignored; without build:prod there are no .br/.gz siblings and the precompressed negotiation is dead code. The workflow checks both and smoke-tests the tarball under node before upload.
  • workflow_dispatch accepts a tag because a tag-triggered run can't backfill an already-published release — v0.7.4 can be attached that way. latest only moves for a real non-prerelease publish.
  • The image needs ubuntu-24.04-arm for the arm64 leg. If unavailable it fails loudly rather than silently emulating; a comment says how to switch to QEMU.
  • No new third-party actions — only checkout and setup-bun at the SHAs already used elsewhere. Uploads go through gh, the image through docker build / buildx imagetools.
  • Overridden rpcs only apply under rpc-gateway. The smoldot backends sync from chain specs and ignore them, so a container pointed at a dead endpoint can still look healthy. Documented in both READMEs.
  • Tarball is ~81 MB — chain specs and smoldot wasm dominate, and the precompressed siblings are already inside, so gzip gains little.
  • Neither artifact has been published from CI yet; the workflow has only been dry-run locally.

Verified

Typecheck, lint, tests (11 packages) and prettier all pass. Beyond that, on a running container and a built tarball: hostname routing to three distinct bundles; the header set matching nginx (frame-ancestors, CORP/COEP/COOP, X-Frame-Options, nosniff, immutable assets, Service-Worker-Allowed); brotli and gzip negotiation; path-traversal attempts falling through to the SPA index; PORT=80 and malformed configs exiting non-zero; the same image serving three different configs without a rebuild; serve.mjs running identically under node and bun; and the baseDomain guards accepting valid suffixes while rejecting non-suffixes, single segments and non-strings.

Also confirmed the premise the endpoint-only limit rests on: all four previewnet genesis hashes match live previewnet, so pointing dot.li at a bitten fork is purely an endpoint swap.

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Performance Report

⚠️ No baseline found on main. This PR's results are recorded but cannot be compared.
Merge to main to establish a baseline.

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Chunks over 500 KB:

File Raw Brotli Gzip
host/assets/paseo.smol-DboPaEh1.json 1.84 MB 941.7 KB 1019.4 KB
host/assets/paseo-people-next.smol.json 3.36 MB 1.68 MB 1.82 MB
host/assets/smoldot.js 2.98 MB 2.22 MB (+111 B) 2.23 MB
host/assets/smoldot_worker.js 2.95 MB 2.21 MB 2.22 MB
host/assets/wasm/web/truapi_server_bg.wasm 2.01 MB 627.5 KB 830.0 KB
Total 14.31 MB (+11.0 KB) 7.98 MB (+3.0 KB) (-44%) 8.46 MB (+3.3 KB)
All files
File Raw Brotli Gzip
host/.well-known/apple-app-site-association 738 B 738 B 738 B
host/.well-known/assetlinks.json 1.3 KB 317 B 391 B
host/assets/bridge.js 69.0 KB 18.9 KB (-6 B) 21.7 KB (-2 B)
host/assets/browser.js 22.9 KB 7.6 KB (+21 B) 8.6 KB
host/assets/client.js 100.1 KB 29.4 KB (+39 B) 32.4 KB (-1 B)
host/assets/dist.js 39.0 KB 12.9 KB (+5 B) 14.6 KB (-1 B)
host/assets/dotli-debug-bus.js 710 B 710 B 710 B
host/assets/get-sync-provider.js 2.8 KB 1.1 KB 1.2 KB (-1 B)
host/assets/hex.js 154 B 154 B 154 B
host/assets/index.js 192.4 KB (+2.3 KB) 48.2 KB (+594 B) 57.7 KB (+825 B)
host/assets/index.css 46.3 KB 7.3 KB 8.1 KB
host/assets/manifest.js 22.5 KB 7.3 KB (+9 B) 8.0 KB (-1 B)
host/assets/panel.js 76.2 KB 20.6 KB (+9 B) 23.2 KB
host/assets/paseo.smol-DboPaEh1.json 1.84 MB 941.7 KB 1019.4 KB
host/assets/paseo-people-next.smol.json 3.36 MB 1.68 MB 1.82 MB
host/assets/paseo.smol.json 22.3 KB 5.1 KB 5.9 KB
host/assets/previewnet.smol.json 179.0 KB 13.8 KB 34.0 KB
host/assets/resolve.js 156 B 156 B 156 B
host/assets/rpc-resolve.js 2.4 KB 1.0 KB (-1 B) 1.2 KB (+1 B)
host/assets/smoldot.js 2.98 MB 2.22 MB (+111 B) 2.23 MB
host/assets/smoldot_worker.js 2.95 MB 2.21 MB 2.22 MB
host/assets/spans.js 2.6 KB (+61 B) 1.1 KB (+11 B) 1.3 KB (+18 B)
host/assets/src.js 1.8 KB 842 B (-18 B) 945 B (+1 B)
host/assets/styles.css 15.3 KB 3.3 KB 3.8 KB
host/assets/wasm/web/README.md 11.5 KB 11.5 KB 11.5 KB
host/assets/wasm/web/package.json 371 B 371 B 371 B
host/assets/wasm/web/truapi_server.d.ts 6.9 KB 6.9 KB 6.9 KB
host/assets/wasm/web/truapi_server.js 35.6 KB 6.2 KB 7.2 KB
host/assets/wasm/web/truapi_server_bg.wasm 2.01 MB 627.5 KB 830.0 KB
host/assets/wasm/web/truapi_server_bg.wasm.d.ts 2.5 KB 2.5 KB 2.5 KB
host/assets/web.js 13.2 KB 3.5 KB (+1 B) 4.0 KB (+2 B)
host/assets/worker-runtime.js 6.3 KB (+6.2 KB) 1.6 KB (+1.5 KB) 1.8 KB (+1.7 KB)
host/assets/worker-runtime.js 106 B 106 B 106 B
host/assets/ws.js 23.1 KB 7.5 KB (+3 B) 8.2 KB (+1 B)
host/dotli.png 11.5 KB 11.5 KB 11.5 KB
host/favicon.svg 1.8 KB 1.8 KB 1.8 KB
host/host-sw.js 2.7 KB 1.1 KB (-2 B) 1.2 KB (-1 B)
host/icon-192.png 12.5 KB 12.5 KB 12.5 KB
host/icon-512.png 42.8 KB 42.8 KB 42.8 KB
host/index.html 19.9 KB 4.4 KB (+13 B) 5.4 KB (-2 B)
host/manifest.webmanifest 441 B 441 B 441 B
host/workbox.js 14.8 KB 4.6 KB 5.1 KB
sandbox/app-sw.js 9.7 KB (+72 B) 3.2 KB (+22 B) 3.6 KB (+25 B)
sandbox/assets/bitswap-bridge.js 840 B 840 B 840 B
sandbox/assets/fetch.js 3.4 KB 1.2 KB (-1 B) 1.4 KB (-2 B)
sandbox/assets/index.css 46.3 KB 7.3 KB 8.1 KB
sandbox/assets/index.js 120.4 KB (+2.4 KB) 34.4 KB (+712 B) 40.4 KB (+785 B)
sandbox/favicon.svg 1.8 KB 1.8 KB 1.8 KB
sandbox/index.html 1.7 KB 581 B 784 B (-2 B)
Total 14.31 MB (+11.0 KB) 7.98 MB (+3.0 KB) (-44%) 8.46 MB (+3.3 KB)

Commit: 39a1a4f

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

E2E Product suite failed on cb1176ed05028d4acdc6f6a45b8b1eaeb6ba6fb4 — 38 passed, 7 failed, 17 skipped.

Failed tests:

  • Get Product Account
  • Product Signer
  • Product Account Alias
  • Bulletin Allowance
  • All Allowances
  • Query Balance
  • Sign Raw Message

Logs: https://github.com/paritytech/dotli-community/actions/runs/31385021276
Artifacts: e2e-product-results (uploaded above) — open the failed test's trace.zip with npx playwright show-trace.

@BigTava BigTava self-assigned this Aug 11, 2026
};

/**
* Runtime overrides for the tables above, so a deployment can point a network at

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this declare a custom network instead of overriding a built-in one, so a fork brings its own genesis, dotns and chain specs rather than borrowing previewnet's name and its chain specs with it?

export const NETWORK_NAME_TO_SERVICES_CONFIG: Record<
NetworkName,
ServicesConfig
> = applyNetworkOverrides(BUILTIN_NETWORK_SERVICES);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why build the table at module init rather than resolving in getActiveServicesConfig(), given the SharedWorker never sees __DOTLI_NETWORK__ and so builds a different table than the document?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants