Prebuilt dot.li bundles with runtime network overrides - #156
Conversation
⚡ Performance Report
|
Bundle Size ReportChunks over 500 KB:
All files
Commit: 39a1a4f |
|
❌ E2E Product suite failed on Failed tests:
Logs: https://github.com/paritytech/dotli-community/actions/runs/31385021276 |
| }; | ||
|
|
||
| /** | ||
| * Runtime overrides for the tables above, so a deployment can point a network at |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
Summary
Publishes two prebuilt dot.li artifacts per release, both configurable at run time so one artifact works against any chain:
ghcr.io/paritytech/dotli-community:<version>dotli-<version>.tar.gzon the releaseserve.mjsBoth 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 only —
label,rpcs,ipfsGateways. Nevergenesisordotns: 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.dotlookup whileisVerifiedSession()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
genesisanddotns, 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
NetworkNamestays a closed union — uselabelto 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:RUNTIME_CONFIG_ENABLEDgates the reader — in a default build the minifier folds the guard andreadRuntimeConfig()compiles toreturn null, so the global read is absent rather than merely bypassed.baseDomaindot.li derives its base domain from the last two hostname segments, which is wrong for anything deeper —
dotli.ppn-65iw.pdp-stg-scw.parity.ioderivesparity.io, then looks for its protocol iframe athost.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-ancestorsis derived fromDOMAINfor the same reason.Serving rules, in three places now
nginx.conf.template(deployed),nginx.docker.conf.template(container) andscripts/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 onframe-ancestorsand COEP. Cross-referenced in comments; a parity check would be the honest follow-up.scripts/preview-server.tsis untouched apart from serving the config path, since two Playwright configs depend on its behaviour.Notes for review
VITE_RUNTIME_NETWORK_CONFIGthe script tag isn't emitted and the served config is silently ignored; withoutbuild:prodthere are no.br/.gzsiblings and the precompressed negotiation is dead code. The workflow checks both and smoke-tests the tarball under node before upload.workflow_dispatchaccepts a tag because a tag-triggered run can't backfill an already-published release — v0.7.4 can be attached that way.latestonly moves for a real non-prerelease publish.ubuntu-24.04-armfor the arm64 leg. If unavailable it fails loudly rather than silently emulating; a comment says how to switch to QEMU.checkoutandsetup-bunat the SHAs already used elsewhere. Uploads go throughgh, the image throughdocker build/buildx imagetools.rpcsonly apply underrpc-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.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=80and malformed configs exiting non-zero; the same image serving three different configs without a rebuild;serve.mjsrunning identically under node and bun; and thebaseDomainguards 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.