diff --git a/docs/infrastructure/overlay-server.md b/docs/infrastructure/overlay-server.md index 99903acfb..ae71dc54d 100644 --- a/docs/infrastructure/overlay-server.md +++ b/docs/infrastructure/overlay-server.md @@ -2,9 +2,9 @@ id: infra-overlay-server title: 'Overlay Server' kind: infra -version: '2.1.43' -last_updated: '2026-09-26' -last_verified: '2026-09-26' +version: '2.1.44' +last_updated: '2026-09-27' +last_verified: '2026-09-27' review_cadence_days: 30 status: stable tags: [overlay, topic-manager, lookup-service, transaction-routing] @@ -75,38 +75,39 @@ None (HTTP-only OverlayExpress endpoints). ## Configuration (env vars) -| Variable | Required | Description | -| -------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| NODE_NAME | Yes | One-word, lowercase overlay service node identifier | -| SERVER_PRIVATE_KEY | Yes | 32-byte hex root private key for server wallet | -| HOSTING_URL | Yes | Public URL where the node is reachable | -| ADMIN_TOKEN | Yes | Independent random secret of at least 32 characters for admin API access | -| WALLET_STORAGE_URL | Yes | BSV wallet storage endpoint (e.g., `https://store-us-1.bsvb.tech`) | -| NETWORK | Yes | `main` or `test` (BSV blockchain network) | -| ARC_API_KEY | Conditionally | Arc key for fallback transaction broadcasting. Required only when `ARCADE_URL` is unset. | -| ARC_CALLBACK_TOKEN | No | Independent random secret of at least 32 characters expected on `/arc-ingest` callbacks | -| ARCADE_URL | Conditionally | Arcade endpoint used as the first-choice broadcaster and proof lookup provider. Required only when `ARC_API_KEY` is unset. | -| ARCADE_API_KEY | No | Arcade API key, when the deployment requires one. | -| ARCADE_DEPLOYMENT_ID | No | Stable Arcade deployment identifier used for callback/proof routing. | -| CHAINTRACKS_URL | No | Explicit go-chaintracks compatible endpoint for headers and reorg SSE. If unset, Arcade can be reused when `USE_ARCADE_CHAINTRACKS=true`. | -| CHAINTRACKS_API_PREFIX | No | Chaintracks API prefix. Defaults to `/chaintracks/v2` for Arcade-mounted Chaintracks. | -| USE_ARCADE_CHAINTRACKS | No | Reuse `ARCADE_URL` for Chaintracks when `CHAINTRACKS_URL` is unset. Defaults to true when `ARCADE_URL` is set. | -| THROW_ON_BROADCAST_FAIL | No | Reject overlay admission if no broadcast provider accepts the transaction. Defaults to `true`. | -| MONGO_URL | Yes | MongoDB connection string | -| KNEX_URL | Yes | MySQL connection string for Knex | -| GASP_ENABLED | No | `true` or `false` (Graph Aware Sync Protocol for overlay sync) | -| MANDALA_ENABLED | No | Enable the regulated Mandala topic. Defaults to `false`; see the production restriction below. | -| MANDALA_VERIFIER_PRIVATE_KEY | When enabled | Dedicated 32-byte linkage-verifier root, distinct from the node and Mandala admin roots. | -| MANDALA_ADMIN_PRIVATE_KEY | When enabled | Dedicated 32-byte administrative root, distinct from the node and verifier roots. | -| MANDALA_STATIC_DENYLIST_JSON | When enabled | Explicit bounded JSON array of compressed identity keys for reference/local screening only. | -| BASM_ENABLED | No | Enable BRC-136 BASM synchronization. Defaults to `false`. | -| BASM_REORG_STREAM_ENABLED | No | Subscribe to Chaintracks reorg SSE when Chaintracks is configured. Defaults to `true`. | -| BASM_REORG_SCAN_DEPTH | No | Number of recent blocks to revalidate on reorg reconnect/poll. | -| BASM_BLOCK_POLL_INTERVAL_MS | No | Interval for BASM anchor/header polling. Set to `0` to disable periodic polling. | -| UNPROVEN_EVICTION_BLOCKS | No | Block-age threshold for unproven transaction eviction. | -| UNPROVEN_MAINTENANCE_INTERVAL_MS | No | Periodic refresh-then-evict cadence for unproven transactions. `0` disables periodic maintenance. | -| LOG_LEVEL | No | pino log level. Defaults to `info`. | -| OTEL_* | No | OpenTelemetry exporter/resource configuration. See `infra/OBSERVABILITY.md`. | +| Variable | Required | Description | +| -------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NODE_NAME | Yes | One-word, lowercase overlay service node identifier | +| SERVER_PRIVATE_KEY | Yes | 32-byte hex root private key for server wallet | +| HOSTING_URL | Yes | Public URL where the node is reachable | +| ADMIN_TOKEN | Yes | Independent random secret of at least 32 characters for admin API access | +| WALLET_STORAGE_URL | Yes | BSV wallet storage endpoint (e.g., `https://store-us-1.bsvb.tech`) | +| NETWORK | Yes | `main`, `test` or `ttn` (BSV blockchain network) | +| ARC_API_KEY | Conditionally | Arc key for fallback transaction broadcasting. Required only when `ARCADE_URL` is unset. | +| ARC_CALLBACK_TOKEN | No | Independent random secret of at least 32 characters expected on `/arc-ingest` callbacks | +| ARCADE_URL | Conditionally | Arcade endpoint used as the first-choice broadcaster and proof lookup provider. Required only when `ARC_API_KEY` is unset. | +| ARCADE_API_KEY | No | Arcade API key, when the deployment requires one. | +| ARCADE_DEPLOYMENT_ID | No | Stable Arcade deployment identifier used for callback/proof routing. | +| CHAINTRACKS_URL | No | Explicit go-chaintracks compatible endpoint for headers and reorg SSE. If unset, Arcade can be reused when `USE_ARCADE_CHAINTRACKS=true`. | +| CHAINTRACKS_API_PREFIX | No | Chaintracks API prefix. Defaults to `/chaintracks/v2` for Arcade-mounted Chaintracks. | +| USE_ARCADE_CHAINTRACKS | No | Reuse `ARCADE_URL` for Chaintracks when `CHAINTRACKS_URL` is unset. Defaults to true when `ARCADE_URL` is set. | +| THROW_ON_BROADCAST_FAIL | No | Reject overlay admission if no broadcast provider accepts the transaction. Defaults to `true`. | +| MONGO_URL | Yes | MongoDB connection string | +| KNEX_URL | Yes | MySQL connection string for Knex | +| GASP_ENABLED | No | `true` or `false` (Graph Aware Sync Protocol for overlay sync) | +| DISCOVERY_ROOT | No | Opt in to public SHIP/SLAP root registration and self-origin advertisement bootstrap. Defaults to `false`; requires a credential-free HTTPS hosting origin. | +| MANDALA_ENABLED | No | Enable the regulated Mandala topic. Defaults to `false`; see the production restriction below. | +| MANDALA_VERIFIER_PRIVATE_KEY | When enabled | Dedicated 32-byte linkage-verifier root, distinct from the node and Mandala admin roots. | +| MANDALA_ADMIN_PRIVATE_KEY | When enabled | Dedicated 32-byte administrative root, distinct from the node and verifier roots. | +| MANDALA_STATIC_DENYLIST_JSON | When enabled | Explicit bounded JSON array of compressed identity keys for reference/local screening only. | +| BASM_ENABLED | No | Enable BRC-136 BASM synchronization. Defaults to `false`. | +| BASM_REORG_STREAM_ENABLED | No | Subscribe to Chaintracks reorg SSE when Chaintracks is configured. Defaults to `true`. | +| BASM_REORG_SCAN_DEPTH | No | Number of recent blocks to revalidate on reorg reconnect/poll. | +| BASM_BLOCK_POLL_INTERVAL_MS | No | Interval for BASM anchor/header polling. Set to `0` to disable periodic polling. | +| UNPROVEN_EVICTION_BLOCKS | No | Block-age threshold for unproven transaction eviction. | +| UNPROVEN_MAINTENANCE_INTERVAL_MS | No | Periodic refresh-then-evict cadence for unproven transactions. `0` disables periodic maintenance. | +| LOG_LEVEL | No | pino log level. Defaults to `info`. | +| OTEL_* | No | OpenTelemetry exporter/resource configuration. See `infra/OBSERVABILITY.md`. | At least one transaction propagation provider must be configured: `ARCADE_URL` or `ARC_API_KEY`. Production deployments should prefer @@ -253,3 +254,14 @@ counts. - [GitHub](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/overlay-server) - [npm package](https://npmjs.com/package/@bsv/overlay-express) + +### Discovery-root bootstrap + +Set `DISCOVERY_ROOT=true` only for an operator-designated public root. This keeps +the default discovery topics/services in advertisement synchronization and points +the advertiser's SHIP/SLAP lookups at the node's own HTTPS origin. It allows a root +with no existing `ls_ship` discovery registration to create that registration. +Normal clients keep their network presets, and ordinary deployments keep their +existing defaults. Validate both direct and default SDK SHIP/SLAP discovery, +transaction propagation and signed UHRP admission before completing promotion. +No schema or wire migration is required. diff --git a/docs/reference/service-operations.md b/docs/reference/service-operations.md index 7818f4243..e2a677625 100644 --- a/docs/reference/service-operations.md +++ b/docs/reference/service-operations.md @@ -169,7 +169,7 @@ Incident handling follows this evidence-preserving sequence: ### overlay-server - Configuration: required `HOSTING_URL`, `KNEX_URL`, `MONGO_URL`, `NETWORK`, `NODE_NAME`, `SERVER_PRIVATE_KEY`, `WALLET_STORAGE_URL`; optional - `ADMIN_TOKEN`, `ARCADE_API_KEY`, `ARCADE_DEPLOYMENT_ID`, `ARCADE_URL`, `ARC_API_KEY`, `ARC_CALLBACK_TOKEN`, `CHAINTRACKS_URL`, `GASP_ENABLED`; secret-bearing + `ADMIN_TOKEN`, `ARCADE_API_KEY`, `ARCADE_DEPLOYMENT_ID`, `ARCADE_URL`, `ARC_API_KEY`, `ARC_CALLBACK_TOKEN`, `CHAINTRACKS_URL`, `GASP_ENABLED`, `DISCOVERY_ROOT`; secret-bearing `ADMIN_TOKEN`, `ARCADE_API_KEY`, `ARC_API_KEY`, `ARC_CALLBACK_TOKEN`, `KNEX_URL`, `MONGO_URL`, `OTEL_EXPORTER_OTLP_HEADERS`, `SERVER_PRIVATE_KEY`. - Telemetry: ESM bootstrap `src/telemetry.ts`, logger @@ -180,6 +180,7 @@ Incident handling follows this evidence-preserving sequence: - query each enabled lookup service - ingest and classify provider callbacks - reconcile GASP/BASM state across a reorganization +- discover SHIP/SLAP services through default SDK network presets when operating as a discovery root - Alerts: - readiness dependency checks fail or provider callback processing errors repeat - unproven rows age beyond policy or maintenance repeatedly fails diff --git a/docs/reference/stack-facts.md b/docs/reference/stack-facts.md index 677c7aa3f..a3792d08d 100644 --- a/docs/reference/stack-facts.md +++ b/docs/reference/stack-facts.md @@ -81,7 +81,7 @@ the separately released and verified image digest. | --- | --- | --- | --- | --- | --- | --- | | BSV Chaintracks Server | `chaintracks-server` | `1.1.24` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/chaintracks-server](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/chaintracks-server) | | BSV Message Box Server | `@bsv/messagebox-server` | `1.1.47` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/message-box-server](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/message-box-server) | -| BSV Overlay Server | `@bsv/overlay-express-examples` | `2.1.43` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/overlay-server](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/overlay-server) | +| BSV Overlay Server | `@bsv/overlay-express-examples` | `2.1.44` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/overlay-server](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/overlay-server) | | BSV UHRP Basic Server | `@bsv/uhrp-lite` | `0.1.43` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/uhrp-server-basic](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/uhrp-server-basic) | | BSV UHRP Cloud Bucket Server | `@bsv/uhrp-storage-server` | `0.2.46` | `>=24 <25` | node, linux/amd64 | ghcr-keyless | [infra/uhrp-server-cloud-bucket](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/uhrp-server-cloud-bucket) | | Wallet Authentication Backend | `@bsv/wab-server` | `1.8.7` | `>=24 <25` | node, linux/amd64 | ghcr-and-aws-marketplace-keyless | [infra/wab](https://github.com/bsv-blockchain/ts-stack/tree/main/infra/wab) | diff --git a/governance/service-operations.json b/governance/service-operations.json index 1e9696311..2eb6671be 100644 --- a/governance/service-operations.json +++ b/governance/service-operations.json @@ -251,7 +251,8 @@ "ARC_API_KEY", "ARC_CALLBACK_TOKEN", "CHAINTRACKS_URL", - "GASP_ENABLED" + "GASP_ENABLED", + "DISCOVERY_ROOT" ], "secrets": [ "ADMIN_TOKEN", @@ -280,7 +281,8 @@ "submit and propagate a transaction without creating local-only state", "query each enabled lookup service", "ingest and classify provider callbacks", - "reconcile GASP/BASM state across a reorganization" + "reconcile GASP/BASM state across a reorganization", + "discover SHIP/SLAP services through default SDK network presets when operating as a discovery root" ], "alerts": [ "readiness dependency checks fail or provider callback processing errors repeat", diff --git a/infra/overlay-server/.env.example b/infra/overlay-server/.env.example index 383e6c3ae..61e1b27ff 100644 --- a/infra/overlay-server/.env.example +++ b/infra/overlay-server/.env.example @@ -16,6 +16,8 @@ NETWORK=main NODE_NAME=your-overlay-node ADMIN_TOKEN=replace-with-at-least-32-random-characters GASP_ENABLED=false +# Opt in only for a public SHIP/SLAP discovery root. +DISCOVERY_ROOT=false # Regulated Mandala processing is disabled by default. The bundled static # denylist adapter is for deliberate reference/local use; production code must # inject an authoritative, continuously maintained ScreeningProvider. diff --git a/infra/overlay-server/README.md b/infra/overlay-server/README.md index 86305f21a..06b27beb9 100644 --- a/infra/overlay-server/README.md +++ b/infra/overlay-server/README.md @@ -45,23 +45,24 @@ The service will start on `http://localhost:8080` by default. For production bui All critical configuration is supplied through environment variables. Create a `.env` file in the project root (or use secrets in your orchestration platform) and define: -| Variable | Example | Description | -| ------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `NODE_NAME` | `my-overlay` | One-word, lowercase overlay service node identifier. | -| `SERVER_PRIVATE_KEY` | required | Dedicated 32-byte hex root private key for the server wallet. Generate and inject it outside source control. | -| `HOSTING_URL` | `https://my.overlay.network` | Public URL where your node is reachable. | -| `ADMIN_TOKEN` | `at-least-32-random-characters` | Random token of at least 32 characters required to access the admin API. | -| `WALLET_STORAGE_URL` | `https://store-us-1.bsvb.tech` | Wallet storage endpoint where advertisement tokens will be kept, and from where funds will be drawn. | -| `NETWORK` | `main`, `test`, or `ttn` | BSV Blockchain network your node operates on. TTN uses the public TTN Arcade endpoint by default and Arcade-backed ChainTracks. | -| `ARC_API_KEY` | — | Your ARC key for transaction broadcasting. | -| `ARC_CALLBACK_TOKEN` | `independent-32-character-random-secret` | Separate random secret of at least 32 characters required to authenticate `/arc-ingest` callbacks. | -| `MONGO_URL` | `mongodb://root:example@localhost:27017` | MongoDB connection string. | -| `KNEX_URL` | `mysql://user:pass@localhost:3306/appdb` | MySQL connection string used by Knex. | -| `GASP_ENABLED` | `true / false` | Enable Graph Aware Sync Protocol to sync with other overlays on the same topics. | -| `MANDALA_ENABLED` | `false` | Explicitly enable the regulated Mandala topic; disabled by default. | -| `MANDALA_VERIFIER_PRIVATE_KEY` | required when enabled | Dedicated 32-byte hex linkage-verifier root, distinct from the node and Mandala admin roots. | -| `MANDALA_ADMIN_PRIVATE_KEY` | required when enabled | Dedicated 32-byte hex Mandala administrative root, distinct from the node and verifier roots. | -| `MANDALA_STATIC_DENYLIST_JSON` | `[]` | Explicit JSON array of canonical compressed identity keys for reference/local screening. | +| Variable | Example | Description | +| ------------------------------ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `NODE_NAME` | `my-overlay` | One-word, lowercase overlay service node identifier. | +| `SERVER_PRIVATE_KEY` | required | Dedicated 32-byte hex root private key for the server wallet. Generate and inject it outside source control. | +| `HOSTING_URL` | `https://my.overlay.network` | Public URL where your node is reachable. | +| `ADMIN_TOKEN` | `at-least-32-random-characters` | Random token of at least 32 characters required to access the admin API. | +| `WALLET_STORAGE_URL` | `https://store-us-1.bsvb.tech` | Wallet storage endpoint where advertisement tokens will be kept, and from where funds will be drawn. | +| `NETWORK` | `main`, `test`, or `ttn` | BSV Blockchain network your node operates on. TTN uses the public TTN Arcade endpoint by default and Arcade-backed ChainTracks. | +| `ARC_API_KEY` | — | Your ARC key for transaction broadcasting. | +| `ARC_CALLBACK_TOKEN` | `independent-32-character-random-secret` | Separate random secret of at least 32 characters required to authenticate `/arc-ingest` callbacks. | +| `MONGO_URL` | `mongodb://root:example@localhost:27017` | MongoDB connection string. | +| `KNEX_URL` | `mysql://user:pass@localhost:3306/appdb` | MySQL connection string used by Knex. | +| `GASP_ENABLED` | `true / false` | Enable Graph Aware Sync Protocol to sync with other overlays on the same topics. | +| `DISCOVERY_ROOT` | `false` | Explicitly register this public node as a SHIP/SLAP discovery root and bootstrap its advertisement lookups at its own HTTPS origin. | +| `MANDALA_ENABLED` | `false` | Explicitly enable the regulated Mandala topic; disabled by default. | +| `MANDALA_VERIFIER_PRIVATE_KEY` | required when enabled | Dedicated 32-byte hex linkage-verifier root, distinct from the node and Mandala admin roots. | +| `MANDALA_ADMIN_PRIVATE_KEY` | required when enabled | Dedicated 32-byte hex Mandala administrative root, distinct from the node and verifier roots. | +| `MANDALA_STATIC_DENYLIST_JSON` | `[]` | Explicit JSON array of canonical compressed identity keys for reference/local screening. | A complete example can be found in `docker-compose.yml`. @@ -131,7 +132,7 @@ upgrade a running overlay or its locked dependencies automatically. ## UHRP discovery compatibility -Image candidate 2.1.43 consumes Overlay 2.6.2, discovery services 2.2.6, +Image 2.1.43 consumes Overlay 2.6.2, discovery services 2.2.6, Overlay Express 2.7.3 and Overlay Topics 1.9.1. These patches restore bounded SDK discovery across deterministic pages, hydrate unconfirmed SQL output heights as absent metadata, and accept the SDK's 200-row UHRP page. Existing signatures, @@ -140,3 +141,21 @@ unchanged. No data migration is required. Build and publish through the protecte image workflow, validate submit/discovery/download in staging, and promote only the same verified digest after acceptance. Source reconciliation does not deploy a running service. + +## Public discovery roots + +Image candidate 2.1.44 adds `DISCOVERY_ROOT=true` for operator-designated public +SHIP/SLAP roots. Ordinary nodes retain suppressed advertisements for the default +discovery topics and services. An enabled root advertises `tm_ship`, `tm_slap`, +`ls_ship` and `ls_slap` alongside its configured application services. Its wallet +advertiser reads existing SHIP/SLAP advertisements directly from `HOSTING_URL`, +avoiding a dependency on discovering `ls_ship` before it can register that service. +Client SDKs continue using their normal network presets without host overrides. + +Root mode requires a credential-free HTTPS origin with no path, query or +fragment. No database or wire migration is required, and existing advertisement +signatures, admission and propagation checks remain enforced. Validate default +SDK discovery and a real advertisement broadcast after staging the verified +image. To retire root mode, unset the flag and run normal advertisement sync; it +revokes the default discovery advertisements while preserving application ones. +A process restart alone should not be treated as proof of their withdrawal. diff --git a/infra/overlay-server/package-lock.json b/infra/overlay-server/package-lock.json index b56245f48..fc8a4dc3b 100644 --- a/infra/overlay-server/package-lock.json +++ b/infra/overlay-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bsv/overlay-express-examples", - "version": "2.1.43", + "version": "2.1.44", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bsv/overlay-express-examples", - "version": "2.1.43", + "version": "2.1.44", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@bsv/overlay": "^2.6.2", diff --git a/infra/overlay-server/package.json b/infra/overlay-server/package.json index d38e01a08..bd4ef057a 100644 --- a/infra/overlay-server/package.json +++ b/infra/overlay-server/package.json @@ -1,7 +1,7 @@ { "name": "@bsv/overlay-express-examples", "private": true, - "version": "2.1.43", + "version": "2.1.44", "overrides": { "gaxios": "7.3.0" }, @@ -26,7 +26,7 @@ "scripts": { "build": "tsc", "lint": "oxlint src --deny-warnings", - "test": "node --import tsx --test src/lifecycle.test.ts src/mandalaStateStore.test.ts src/securityConfig.test.ts", + "test": "node --import tsx --test src/lifecycle.test.ts src/mandalaStateStore.test.ts src/securityConfig.test.ts src/discoveryConfig.test.ts", "start": "node --import ./dist/telemetry.js dist/index.js", "dev": "tsx src/index.ts" }, diff --git a/infra/overlay-server/src/discoveryConfig.test.ts b/infra/overlay-server/src/discoveryConfig.test.ts new file mode 100644 index 000000000..a0066fc93 --- /dev/null +++ b/infra/overlay-server/src/discoveryConfig.test.ts @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { readDiscoveryRuntimeConfiguration } from './discoveryConfig.js' + +test('ordinary nodes retain suppressed discovery-service advertisements and default resolvers', () => { + for (const environment of [{}, { DISCOVERY_ROOT: 'false' }]) { + assert.deepEqual( + readDiscoveryRuntimeConfiguration(environment, 'ttn', 'http://localhost:8080'), + { + suppressDefaultSyncAdvertisements: true + } + ) + } +}) + +test('explicit roots bootstrap both advertisement protocols at their own HTTPS origin', () => { + for (const [network, preset] of [ + ['main', 'mainnet'], + ['test', 'testnet'], + ['ttn', 'teratestnet'] + ] as const) { + assert.deepEqual( + readDiscoveryRuntimeConfiguration( + { DISCOVERY_ROOT: 'true' }, + network, + 'https://root.example/' + ), + { + suppressDefaultSyncAdvertisements: false, + lookupResolverConfig: { + networkPreset: preset, + hostOverrides: { ls_ship: ['https://root.example'] }, + slapTrackers: ['https://root.example'] + } + } + ) + } +}) + +test('root mode rejects ambiguous flags and credential-bearing or non-origin bootstrap URLs', () => { + assert.throws( + () => + readDiscoveryRuntimeConfiguration({ DISCOVERY_ROOT: 'treu' }, 'ttn', 'https://root.example'), + /DISCOVERY_ROOT must be one of/ + ) + for (const url of [ + 'not-a-url', + 'http://root.example', + 'https://user:password@root.example', + 'https://root.example/lookup', + 'https://root.example?token=value', + 'https://root.example#fragment' + ]) { + assert.throws( + () => readDiscoveryRuntimeConfiguration({ DISCOVERY_ROOT: 'true' }, 'ttn', url), + /credential-free HTTPS hosting origin/ + ) + } +}) diff --git a/infra/overlay-server/src/discoveryConfig.ts b/infra/overlay-server/src/discoveryConfig.ts new file mode 100644 index 000000000..59b351435 --- /dev/null +++ b/infra/overlay-server/src/discoveryConfig.ts @@ -0,0 +1,45 @@ +import type { LookupResolverConfig } from '@bsv/sdk' +import { readBooleanEnv } from './securityConfig.js' + +export interface DiscoveryRuntimeConfiguration { + suppressDefaultSyncAdvertisements: boolean + lookupResolverConfig?: LookupResolverConfig +} + +/** Public discovery roots must advertise and bootstrap their own discovery services. */ +export function readDiscoveryRuntimeConfiguration( + environment: Record, + network: 'main' | 'test' | 'ttn', + hostingURL: string +): DiscoveryRuntimeConfiguration { + if (!readBooleanEnv(environment, 'DISCOVERY_ROOT', false)) { + return { suppressDefaultSyncAdvertisements: true } + } + + let hosting: URL + try { + hosting = new URL(hostingURL) + } catch { + throw new TypeError('DISCOVERY_ROOT requires a credential-free HTTPS hosting origin') + } + if ( + hosting.protocol !== 'https:' || + hosting.username !== '' || + hosting.password !== '' || + hosting.search !== '' || + hosting.hash !== '' || + (hosting.pathname !== '' && hosting.pathname !== '/') + ) { + throw new TypeError('DISCOVERY_ROOT requires a credential-free HTTPS hosting origin') + } + + const networkPreset = { main: 'mainnet', test: 'testnet', ttn: 'teratestnet' } as const + return { + suppressDefaultSyncAdvertisements: false, + lookupResolverConfig: { + networkPreset: networkPreset[network], + hostOverrides: { ls_ship: [hosting.origin] }, + slapTrackers: [hosting.origin] + } + } +} diff --git a/infra/overlay-server/src/index.ts b/infra/overlay-server/src/index.ts index 68d210891..4108da9a2 100644 --- a/infra/overlay-server/src/index.ts +++ b/infra/overlay-server/src/index.ts @@ -52,6 +52,7 @@ import packageJson from '../package.json' with { type: 'json' } import { log } from './logger.js' import { createOverlayLifecycle, type OverlayLifecycle } from './lifecycle.js' import { readBooleanEnv, readMandalaRuntimeConfiguration } from './securityConfig.js' +import { readDiscoveryRuntimeConfiguration } from './discoveryConfig.js' config() const tracer = trace.getTracer(packageJson.name, packageJson.version) @@ -247,17 +248,20 @@ const main = async () => { const providerServer = server as OverlayProviderConfigMethods server.configureLogger(overlayLogger as unknown as typeof console) + const discovery = readDiscoveryRuntimeConfiguration(process.env, NETWORK, HOSTING_URL) const wa = new WalletAdvertiser( - NETWORK as 'main' | 'test', + NETWORK, SERVER_PRIVATE_KEY, WALLET_STORAGE_URL, - HOSTING_URL + HOSTING_URL, + discovery.lookupResolverConfig ) await wa.init() server.configureEngineParams({ advertiser: wa, + suppressDefaultSyncAdvertisements: discovery.suppressDefaultSyncAdvertisements, throwOnBroadcastFailure: boolEnv('THROW_ON_BROADCAST_FAIL', true) })