Files
spaces-startos/startos/main.ts
T
spacesops 70b8aab0f3
Build Service / BuildPackage (push) Has been cancelled
v0.0.9.2
2026-05-24 19:10:21 -04:00

731 lines
25 KiB
TypeScript

import { storeJson } from './fileModels/storeJson'
import { i18n } from './i18n'
import { sdk } from './sdk'
import {
APP_USER,
BITCOIND_RPC_HOSTNAME,
BITCOIND_RPC_PORT,
CERTRELAY_ANCHOR_REFRESH,
CERTRELAY_BIN,
CERTRELAY_BIND,
CERTRELAY_CHAIN,
CERTRELAY_DATA_DIR,
CERTRELAY_DEFAULT_BOOTSTRAP,
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
CERTRELAY_DEFAULT_SELF_URL,
CERTRELAY_DIR,
CERTRELAY_FABRIC_BIN,
CERTRELAY_FABRIC_DEST,
CERTRELAY_PORT,
CERTRELAY_REMOTE_IP_HEADER,
dataDir,
pgDataDir,
POSTGRES_PORT,
SPACED_CHAIN,
spacedRpcPort,
SUBSPACES_DATA_DIR,
SUBSPACES_DIR,
SUBSPACES_PROVER_BIN,
SUBSPACES_PROVER_DIR,
SUBSPACES_PROVER_PORT,
SUBSPACES_REGISTRY_BIN,
SUBSPACES_REGISTRY_DIR,
SUBSPACES_REGISTRY_PORT,
SUBSPACES_SUBS_BIN,
SUBSPACES_UI_PORT,
SUBSPACES_WALLET,
uiPort,
} from './utils'
export const main = sdk.setupMain(async ({ effects }) => {
console.info(i18n('Starting Spaces!'))
const store = await storeJson.read().const(effects)
if (
!store?.password ||
!store?.btcAuth ||
!store?.dbAuth ||
!store?.spacedAuth
) {
// taskSetPassword + taskBtcAuth + taskSeedDb + taskSeedSpacedAuth all seed
// these in init; if they aren't populated yet, init hasn't finished — let
// StartOS restart us.
throw new Error('Spaces store.json is not yet populated.')
}
const { password: APP_PASSWORD, btcAuth, dbAuth, spacedAuth } = store
const enableExplorer = store.enableExplorer === true
const enableSubspaces = store.enableSubspaces === true
// subs-prover is independently gated and defaults OFF (null/undefined => false).
const enableSubsProver = store.enableSubsProver === true
const spacedEnv = {
SPACED_CHAIN,
SPACED_DATA_DIR: dataDir,
SPACED_RPC_BIND: '127.0.0.1',
SPACED_RPC_PORT: String(spacedRpcPort),
SPACED_RPC_URL: `http://127.0.0.1:${spacedRpcPort}`,
SPACED_BLOCK_INDEX: 'true',
SPACED_BITCOIN_RPC_URL: `http://${BITCOIND_RPC_HOSTNAME}:${BITCOIND_RPC_PORT}`,
SPACED_BITCOIN_RPC_USER: btcAuth.username,
SPACED_BITCOIN_RPC_PASSWORD: btcAuth.password,
SPACED_RPC_USER: spacedAuth.username,
SPACED_RPC_PASSWORD: spacedAuth.password,
// legacy aliases for `bitcoin-cli` / shell helpers that read these names
BTC_RPC_HOST: BITCOIND_RPC_HOSTNAME,
BTC_RPC_PORT: String(BITCOIND_RPC_PORT),
BTC_RPC_USER: btcAuth.username,
BTC_RPC_PASSWORD: btcAuth.password,
APP_USER,
APP_PASSWORD,
DB_URL: `postgres://${dbAuth.username}:${dbAuth.password}@127.0.0.1:${POSTGRES_PORT}/${dbAuth.database}`,
}
// Certrelay config — user-tunable bits live in store.json (set via the
// "Configure Certrelay" action); the rest are fixed package defaults.
const certrelaySelfUrl =
store.certrelaySelfUrl ?? CERTRELAY_DEFAULT_SELF_URL
const certrelayBootstrap =
store.certrelayBootstrap ?? CERTRELAY_DEFAULT_BOOTSTRAP
const certrelayHealthcheckHandle =
store.certrelayHealthcheckHandle ?? CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE
const mounts = sdk.Mounts.of().mountVolume({
volumeId: 'main',
subpath: null,
mountpoint: dataDir,
readonly: false,
})
const spacedSub = await sdk.SubContainer.of(
effects,
{ imageId: 'spaces' },
mounts,
'spaced-sub',
)
const termSub = await sdk.SubContainer.of(
effects,
{ imageId: 'spaces' },
mounts,
'terminal-sub',
)
const postgresSub = await sdk.SubContainer.of(
effects,
{ imageId: 'postgres' },
mounts,
'postgres-sub',
)
const postgresEnv = {
POSTGRES_USER: dbAuth.username,
POSTGRES_PASSWORD: dbAuth.password,
POSTGRES_DB: dbAuth.database,
PGDATA: pgDataDir,
}
// TODO(prebuilt-indexer / prebuilt-explorer): the Go indexer and SvelteKit
// explorer used to be built from source in dedicated builder images
// (golang:1.23-alpine, node:20-alpine). Those images + their build chains
// were removed to shrink the .s9pk and cut first-enable latency. When the
// prebuilt images exist, re-add their SubContainers + env here, e.g.:
// const indexerSub = await sdk.SubContainer.of(
// effects, { imageId: '<prebuilt-indexer-image>' }, mounts, 'indexer-sub')
// const explorerSub = await sdk.SubContainer.of(
// effects, { imageId: '<prebuilt-explorer-image>' }, mounts, 'explorer-sub')
// const indexerEnv = { POSTGRES_URI, BITCOIN_NODE_URI/USER/PASSWORD,
// SPACES_NODE_URI, RPC_USER/PASSWORD, ACTIVATION_BLOCK_HEIGHT,
// FAST_SYNC_BLOCK_HEIGHT, UPDATE_DB_INTERVAL, MEMPOOL_CHUNK_SIZE }
// const explorerEnv = { DB_URL (?sslmode=disable), PUBLIC_BTC_NETWORK, PORT }
// Also add the image ids to startos/manifest/index.ts and re-add the daemon
// definitions in the explorer branch below. See git history for prior shapes.
const subspacesSub = await sdk.SubContainer.of(
effects,
{ imageId: 'subspaces' },
mounts,
'subspaces-sub',
)
// subs reads all of these from the environment (see subs/src/main.rs #[arg(env=...)]).
const subsEnv = {
SUBS_DATA_DIR: SUBSPACES_DATA_DIR,
SUBS_PORT: String(SUBSPACES_UI_PORT),
SUBS_WALLET: SUBSPACES_WALLET,
SUBS_SPACED_RPC_URL: `http://127.0.0.1:${spacedRpcPort}`,
SUBS_SPACED_RPC_USER: spacedAuth.username,
SUBS_SPACED_RPC_PASSWORD: spacedAuth.password,
SUBS_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`,
SUBS_REGISTRY_ENDPOINT: `http://127.0.0.1:${SUBSPACES_REGISTRY_PORT}`,
HOME: SUBSPACES_DATA_DIR,
RUST_LOG: 'subs=info,subs_prover=info,registry_server=info',
}
const proverEnv = {
SUBS_PROVER_SERVER: '1',
SUBS_PROVER_PORT: String(SUBSPACES_PROVER_PORT),
HOME: SUBSPACES_PROVER_DIR,
RUST_LOG: 'subs_prover=info',
}
const registryEnv = {
REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT),
HOME: SUBSPACES_REGISTRY_DIR,
RUST_LOG: 'registry_server=info',
}
const certrelaySub = await sdk.SubContainer.of(
effects,
{ imageId: 'certrelay' },
mounts,
'certrelay-sub',
)
const certrelayEnv = {
CERTRELAY_CHAIN,
CERTRELAY_DATA_DIR,
CERTRELAY_BIND,
CERTRELAY_PORT: String(CERTRELAY_PORT),
CERTRELAY_SELF_URL: certrelaySelfUrl,
// Credentials come from the local spaced server (store.spacedAuth).
CERTRELAY_SPACED_RPC_URL: `http://${spacedAuth.username}:${spacedAuth.password}@127.0.0.1:${spacedRpcPort}`,
CERTRELAY_REMOTE_IP_HEADER,
CERTRELAY_BOOTSTRAP: certrelayBootstrap ? 'true' : 'false',
CERTRELAY_ANCHOR_REFRESH,
CERTRELAY_HEALTHCHECK_HANDLE: certrelayHealthcheckHandle,
HOME: CERTRELAY_DATA_DIR,
RUST_LOG: 'info',
}
const bashrc = [
'export PATH=/root/.cargo/bin:/data/bin:/usr/local/bin:/usr/bin:/bin',
"export PS1='spaces:\\w$ '",
`alias spaces='space-cli --chain ${SPACED_CHAIN} --rpc-user "$SPACED_RPC_USER" --rpc-password "$SPACED_RPC_PASSWORD" '`,
'cat <<EOF',
'',
'┌─ Spaces ─────────────────────────────────────────────────┐',
'│ spaced is managed by StartOS — do not run it manually. │',
'│ Use the `spaces` alias to call space-cli, e.g. │',
'│ spaces getserverinfo │',
'│ `fabric` resolves handles against the local certrelay. │',
'│ Docs: https://docs.spacesprotocol.org/ │',
'└──────────────────────────────────────────────────────────┘',
'',
'EOF',
].join('\n')
// Appends the always-on certrelay chain (setup oneshot + certrelay daemon) to
// any existing chain. certrelay serves cryptographic proofs binding handles to
// owner keys anchored to Bitcoin. The `fabric` CLI is copied onto /data/bin so
// the gotty terminal (which has /data/bin on PATH) can resolve handles.
const withCertrelay = (chain: any): any =>
chain
.addOneshot('certrelay-setup', {
subcontainer: certrelaySub,
exec: {
command: [
'sh',
'-c',
`set -eu; \
mkdir -p ${CERTRELAY_DATA_DIR} /data/bin; \
echo "certrelay-setup: installing fabric CLI to ${CERTRELAY_FABRIC_DEST}..."; \
cp -f ${CERTRELAY_FABRIC_BIN} ${CERTRELAY_FABRIC_DEST}; \
chmod +x ${CERTRELAY_FABRIC_DEST}; \
echo "certrelay-setup: done."`,
],
user: 'root',
},
requires: [],
})
.addDaemon('certrelay', {
subcontainer: certrelaySub,
exec: {
command: [CERTRELAY_BIN],
env: certrelayEnv,
// /data is root-owned; the image's USER certrelay can't write it.
user: 'root',
},
ready: {
display: i18n('Certrelay'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, CERTRELAY_PORT, {
successMessage: i18n('certrelay is ready'),
errorMessage: i18n('certrelay is not ready'),
}),
// Match the image's 120s start-period: anchor-refresh/checkpoint work
// on first start can delay the listener.
gracePeriod: 120_000,
trigger: sdk.trigger.cooldownTrigger(30_000),
},
requires: ['certrelay-setup', 'spaced'],
})
// Appends the Subspaces chain (fetch → build → wallet ensure → subs-prover →
// subs daemon) to any existing chain. `as any` because the chain's TS type
// depends on prior IDs and we can't easily express the union here.
const withSubspaces = (chain: any): any => {
let c = chain
.addOneshot('subspaces-dirs', {
subcontainer: subspacesSub,
exec: {
command: [
'sh',
'-c',
`set -eu; \
mkdir -p ${SUBSPACES_DATA_DIR} ${SUBSPACES_PROVER_DIR} ${SUBSPACES_REGISTRY_DIR}; \
echo "subspaces-dirs: ensured data/prover/registry dirs under ${SUBSPACES_DIR}."`,
],
user: 'root',
},
requires: [],
})
.addDaemon('subs-registry', {
subcontainer: subspacesSub,
exec: {
command: [
'sh',
'-c',
`cd ${SUBSPACES_REGISTRY_DIR} && exec ${SUBSPACES_REGISTRY_BIN} --port ${SUBSPACES_REGISTRY_PORT}`,
],
env: registryEnv,
user: 'root',
},
ready: {
display: i18n('Subspaces Registry'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, SUBSPACES_REGISTRY_PORT, {
successMessage: i18n('registry-server is ready'),
errorMessage: i18n('registry-server is not ready'),
}),
gracePeriod: 60_000,
},
requires: ['subspaces-dirs'],
})
.addDaemon('subs', {
subcontainer: subspacesSub,
exec: {
command: [SUBSPACES_SUBS_BIN],
env: subsEnv,
user: 'root',
},
ready: {
display: i18n('Subspaces Web UI'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, SUBSPACES_UI_PORT, {
successMessage: i18n('subspaces UI is ready'),
errorMessage: i18n('subspaces UI is not ready'),
}),
gracePeriod: 60_000,
},
// Intentionally NOT contingent on subs-prover: the RISC Zero prover can
// take a while to become ready, and subs only needs it on-demand (proof
// generation), not at startup. subs reaches the prover via
// SUBS_PROVER_ENDPOINT once the prover is up.
requires: ['subspaces-dirs', 'spaced', 'subs-registry'],
})
// subs-prover is opt-in and DISABLED BY DEFAULT (toggle via the "Enable
// Subspaces Prover" action). Its interface (8888) stays registered either
// way (see interfaces.ts), but the daemon only starts when enabled. It's
// added LAST and depends on every other subspaces daemon; it runs lengthy
// timing tests on boot and NOTHING depends on it, so its slow startup never
// blocks anything else.
if (enableSubsProver) {
c = c.addDaemon('subs-prover', {
subcontainer: subspacesSub,
exec: {
command: [
'sh',
'-c',
`cd ${SUBSPACES_PROVER_DIR} && exec ${SUBSPACES_PROVER_BIN} --server --server-port ${SUBSPACES_PROVER_PORT}`,
],
env: proverEnv,
// Run as root: the horologger/subs image declares USER subs, but our
// /data volume + subdirs are root-owned, so the non-root user can't
// traverse/write them. Root sidesteps the ownership mismatch.
user: 'root',
},
ready: {
display: i18n('Subspaces Prover'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, SUBSPACES_PROVER_PORT, {
successMessage: i18n('subs-prover is ready'),
errorMessage: i18n('subs-prover is not ready'),
}),
gracePeriod: 60_000,
trigger: sdk.trigger.cooldownTrigger(30_000),
},
requires: ['subspaces-dirs', 'subs-registry', 'subs'],
})
}
return c
}
if (!enableExplorer) {
// Spaces-only mode: spaced + gotty terminal. No PostgreSQL, no indexer.
// User can flip the toggle via the Enable Embedded Explorer action;
// store.json.enableExplorer is read via .const() so the merge triggers
// an automatic service restart and the full chain takes over.
let chain: any = sdk.Daemons.of(effects)
.addOneshot('bashrc', {
subcontainer: termSub,
exec: {
command: ['bash', '-c', `cat > /root/.bashrc <<'SPACES_BASHRC_EOF'
${bashrc}
SPACES_BASHRC_EOF`],
user: 'root',
},
requires: [],
})
.addDaemon('spaced', {
subcontainer: spacedSub,
exec: {
command: ['/root/.cargo/bin/spaced'],
env: spacedEnv,
},
ready: {
display: i18n('Spaced RPC'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, spacedRpcPort, {
successMessage: i18n('spaced RPC is ready'),
errorMessage: i18n('spaced RPC is not ready'),
}),
gracePeriod: 120_000,
},
requires: [],
})
.addDaemon('web-terminal', {
subcontainer: termSub,
exec: {
command: [
'gotty',
'--port',
String(uiPort),
'-c',
`${APP_USER}:${APP_PASSWORD}`,
'--permit-write',
'--reconnect',
'/bin/bash',
],
env: spacedEnv,
},
ready: {
display: i18n('Web Interface'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, uiPort, {
successMessage: i18n('The web terminal is ready'),
errorMessage: i18n('The web terminal is not ready'),
}),
},
requires: ['bashrc'],
})
.addHealthCheck('sync', {
ready: {
display: i18n('Spaced Sync'),
fn: async () => {
try {
const probe = await spacedSub.exec(
[
'/root/.cargo/bin/space-cli',
'--chain',
SPACED_CHAIN,
'--rpc-user',
spacedAuth.username,
'--rpc-password',
spacedAuth.password,
'--output-format',
'json',
'getserverinfo',
],
{},
)
const stderr = (probe.stderr ?? '').toString().trim()
const stdoutText = (probe.stdout ?? '').toString()
const stdoutTrimmed = stdoutText.trim()
if (probe.exitCode !== 0) {
return {
result: 'failure',
message: i18n('space-cli exited ${code}: ${error}', {
code: String(probe.exitCode),
error: (stderr || stdoutTrimmed || '<no output>').slice(0, 200),
}),
}
}
let parsed: {
ready?: boolean
progress?: number
chain?: { blocks?: number; headers?: number }
}
try {
parsed = JSON.parse(stdoutText)
} catch {
return {
result: 'failure',
message: i18n(
'getserverinfo non-JSON. stdout=${stdout} stderr=${stderr}',
{
stdout: (stdoutTrimmed || '<empty>').slice(0, 160),
stderr: (stderr || '<empty>').slice(0, 160),
},
),
}
}
const progress = Math.min(
100,
Math.max(0, Math.round((parsed.progress ?? 0) * 100)),
)
const blocks = parsed.chain?.blocks ?? 0
const headers = parsed.chain?.headers ?? 0
if (parsed.ready === true && progress >= 100) {
return {
result: 'success',
message: i18n(
'spaced is fully synced (blocks ${blocks} / headers ${headers}).',
{ blocks: String(blocks), headers: String(headers) },
),
}
}
return {
result: 'loading',
message: i18n(
'spaced is indexing: ${pct}% (blocks ${blocks} / headers ${headers}).',
{
pct: String(progress),
blocks: String(blocks),
headers: String(headers),
},
),
}
} catch (e) {
return {
result: 'failure',
message: i18n('Spaced Sync health check crashed: ${error}', {
error: (e as Error)?.message ?? String(e),
}),
}
}
},
gracePeriod: 30_000,
trigger: sdk.trigger.cooldownTrigger(30_000),
},
requires: ['spaced'],
})
chain = withCertrelay(chain)
if (enableSubspaces) chain = withSubspaces(chain)
return chain
}
// enableExplorer = true: full chain — spaced + terminal + embedded
// PostgreSQL + Go indexer + indexer-sync health check.
let chain: any = sdk.Daemons.of(effects)
.addOneshot('bashrc', {
subcontainer: termSub,
exec: {
command: ['bash', '-c', `cat > /root/.bashrc <<'SPACES_BASHRC_EOF'
${bashrc}
SPACES_BASHRC_EOF`],
user: 'root',
},
requires: [],
})
.addOneshot('postgres-chown', {
subcontainer: postgresSub,
exec: {
command: [
'bash',
'-c',
`mkdir -p ${pgDataDir} && chown -R postgres:postgres ${pgDataDir} && chmod 700 ${pgDataDir}`,
],
user: 'root',
},
requires: [],
})
.addDaemon('postgres', {
subcontainer: postgresSub,
exec: {
command: [
'docker-entrypoint.sh',
'postgres',
'-c',
'listen_addresses=127.0.0.1',
],
env: postgresEnv,
},
ready: {
display: i18n('Database'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, POSTGRES_PORT, {
successMessage: i18n('postgres is ready'),
errorMessage: i18n('postgres is not ready'),
}),
gracePeriod: 60_000,
},
requires: ['postgres-chown'],
})
.addDaemon('spaced', {
subcontainer: spacedSub,
exec: {
command: ['/root/.cargo/bin/spaced'],
env: spacedEnv,
},
ready: {
display: i18n('Spaced RPC'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, spacedRpcPort, {
successMessage: i18n('spaced RPC is ready'),
errorMessage: i18n('spaced RPC is not ready'),
}),
gracePeriod: 120_000,
},
requires: [],
})
// TODO(prebuilt-indexer): the Go indexer build-from-source chain
// (indexer-fetch + indexer-build + indexer-cleanup-legacy +
// indexer-migrate + `indexer` daemon) was removed to shrink the .s9pk
// and cut first-enable latency. Re-add an `indexer` daemon here that runs
// the prebuilt `sync` binary from the future indexer image (image id +
// binary path TBD) against PostgreSQL + spaced. Env it needs is the old
// indexerEnv (POSTGRES_URI, BITCOIN_NODE_*, SPACES_NODE_URI, RPC_USER/
// PASSWORD, ACTIVATION_BLOCK_HEIGHT, FAST_SYNC_BLOCK_HEIGHT,
// UPDATE_DB_INTERVAL, MEMPOOL_CHUNK_SIZE). A goose migration step must
// run before it. See git history (build-from-source) for the prior shape.
//
// TODO(prebuilt-explorer): the SvelteKit explorer build chain
// (explorer-fetch + explorer-install + `explorer-ui` daemon) was removed
// for the same reason. Re-add an `explorer-ui` daemon that runs the
// prebuilt explorer server from the provided explorer image (image id +
// command TBD) on port 3000, reading DB_URL + PUBLIC_BTC_NETWORK.
.addDaemon('web-terminal', {
subcontainer: termSub,
exec: {
command: [
'gotty',
'--port',
String(uiPort),
'-c',
`${APP_USER}:${APP_PASSWORD}`,
'--permit-write',
'--reconnect',
'/bin/bash',
],
env: spacedEnv,
},
ready: {
display: i18n('Web Interface'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, uiPort, {
successMessage: i18n('The web terminal is ready'),
errorMessage: i18n('The web terminal is not ready'),
}),
},
requires: ['bashrc'],
})
.addHealthCheck('sync', {
ready: {
display: i18n('Spaced Sync'),
fn: async () => {
try {
const probe = await spacedSub.exec(
[
'/root/.cargo/bin/space-cli',
'--chain',
SPACED_CHAIN,
'--rpc-user',
spacedAuth.username,
'--rpc-password',
spacedAuth.password,
'--output-format',
'json',
'getserverinfo',
],
{},
)
const stderr = (probe.stderr ?? '').toString().trim()
const stdoutText = (probe.stdout ?? '').toString()
const stdoutTrimmed = stdoutText.trim()
if (probe.exitCode !== 0) {
return {
result: 'failure',
message: i18n('space-cli exited ${code}: ${error}', {
code: String(probe.exitCode),
error: (stderr || stdoutTrimmed || '<no output>').slice(0, 200),
}),
}
}
let parsed: {
ready?: boolean
progress?: number
chain?: { blocks?: number; headers?: number }
}
try {
parsed = JSON.parse(stdoutText)
} catch {
return {
result: 'failure',
message: i18n(
'getserverinfo non-JSON. stdout=${stdout} stderr=${stderr}',
{
stdout: (stdoutTrimmed || '<empty>').slice(0, 160),
stderr: (stderr || '<empty>').slice(0, 160),
},
),
}
}
const progress = Math.min(
100,
Math.max(0, Math.round((parsed.progress ?? 0) * 100)),
)
const blocks = parsed.chain?.blocks ?? 0
const headers = parsed.chain?.headers ?? 0
if (parsed.ready === true && progress >= 100) {
return {
result: 'success',
message: i18n(
'spaced is fully synced (blocks ${blocks} / headers ${headers}).',
{ blocks: String(blocks), headers: String(headers) },
),
}
}
return {
result: 'loading',
message: i18n(
'spaced is indexing: ${pct}% (blocks ${blocks} / headers ${headers}).',
{
pct: String(progress),
blocks: String(blocks),
headers: String(headers),
},
),
}
} catch (e) {
return {
result: 'failure',
message: i18n(
'Spaced Sync health check crashed: ${error}',
{ error: (e as Error)?.message ?? String(e) },
),
}
}
},
gracePeriod: 30_000,
trigger: sdk.trigger.cooldownTrigger(30_000),
},
requires: ['spaced'],
})
// TODO(prebuilt-indexer): re-add the `indexer-sync` standalone health
// check once the prebuilt indexer daemon is wired back in. It queried
// MAX(blocks.height) (non-orphan) via psql and compared against spaced's
// tip from `space-cli getserverinfo`. See git history for the prior body.
chain = withCertrelay(chain)
if (enableSubspaces) chain = withSubspaces(chain)
return chain
})