Skip to content

fix: resolve dependency addresses from the bridge address, not assignedPort - #4

Merged
MattDHill merged 2 commits into
mainfrom
fix/bridge-address-resolution
Jul 25, 2026
Merged

fix: resolve dependency addresses from the bridge address, not assignedPort#4
MattDHill merged 2 commits into
mainfrom
fix/bridge-address-resolution

Conversation

@helix-nine

@helix-nine helix-nine commented Jul 25, 2026

Copy link
Copy Markdown

Why

This package resolved its dependencies' addresses by reading bindings[<port>].net.assignedPort. That field is raw metadata, and which port field is populated depends on how the dependency bound the port — a binding with addSsl frees assignedPort entirely and carries only assignedSslPort.

LND hit exactly this when it moved REST behind the OS reverse proxy (Start9Labs/lnd-startos#171): every dependent silently resolved null.

What changed

Adopts sdk.host.getBridgeAddress, added in start-sdk 2.0.9 (Start9Labs/start-technologies#3560, #3561), and deletes the local copy of the helper this package was carrying — one of 44 near-identical copies across the fleet.

The helper resolves the binding's own derived bridge address, which is correct whether the dependency terminates its own TLS or hands the port to the OS proxy. It is computed per binding rather than per exported interface, so it also resolves bridge-only bindings such as tor's SOCKS proxy.

ssl: is passed only where the target binding publishes two bridge addresses — protocol: 'http'/'ws', or secure: null with addSsl. bitcoind's RPC is the clearest case (10.0.3.1:8332 ssl=false alongside 10.0.3.1:54404 ssl=true), so an undiscriminated lookup there is order-dependent. Where a binding publishes one address no discriminator is passed — pinning one would assert a fact about how the dependency binds, the coupling this change removes.

On the lockfile diff

Larger than expected, and expected to stay that way for now. This package pins git dependencies that track #next branches, which still resolve start-sdk 2.0.7, so npm nests a second SDK copy rather than hoisting one. It collapses once those next branches carry 2.0.9. The lockfile has to be committed regardless — s9pk.mk and the reusable CI both run npm ci, which fails on a lockfile out of sync with package.json. Functionally inert: the git deps are imported for types, and the s9pk build tree-shakes.

Verification

tsc and prettier clean against the published start-sdk 2.0.9. The resolution logic was verified live on a StartOS 0.4.0-beta.10 box: with LND on addSsl, net.assignedPort reads null while the binding's bridge entry resolves to 10.0.3.1:8080; LNbits connected through it (✔️ Backend LndRestWallet connected), and Fulcrum picked bitcoind's plaintext :8332 leg over the TLS :54404 one.

Not exercised against a running instance of this service — compile-checked only.

Test plan

  1. Install this build alongside its dependencies.
  2. Confirm it connects to each one and its health checks go green.
  3. Restart a dependency and confirm the address re-resolves without a restart loop — .const() should settle on one value and stay there.

@helix-nine
helix-nine force-pushed the fix/bridge-address-resolution branch 2 times, most recently from d68aaf9 to 4e902f3 Compare July 25, 2026 02:32
MattDHill
MattDHill previously approved these changes Jul 25, 2026

@MattDHill MattDHill left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed: resolves dependency addresses via sdk.host.getBridgeAddress rather than the raw net.assignedPort / assignedSslPort fields, which are only populated depending on how the dependency bound the port. Verified live on a 0.4.0-beta.10 box across lnd, cln, fulcrum and lnbits.

`net.assignedPort` and `net.assignedSslPort` are raw metadata, and
which of them is populated depends on how the dependency bound the port: a
binding with addSsl frees `assignedPort` entirely. Reading either field
directly breaks the moment a dependency changes its TLS arrangement, as
LND just did (Start9Labs/lnd-startos#171).

start-sdk 2.0.9 adds `sdk.host.getBridgeAddress`, which resolves the
binding's own derived address — correct under either arrangement, and
computed per binding so it also covers bridge-only bindings such as tor's
SOCKS proxy. Adopt it and delete the local copy of the helper this
package was carrying.

`ssl` is passed only where the target publishes both a plaintext and a
TLS address; elsewhere pinning it would assert a fact about how the
dependency binds.
@helix-nine

Copy link
Copy Markdown
Author

Thanks for the review. One change since the sha you approved, plus what I checked before treating this as shippable.

Delta 4e902f368a53fa — the helper deletion left its import behind:

--- a/startos/utils.ts
+++ b/startos/utils.ts
@@ -1,5 +1,3 @@
-import { sdk } from './sdk'
-
 export const uiPort = 80

sdk was only ever used by bridgeAddress(). T came out with the helper, sdk didn't, and nothing flags it — the SDK's tsconfig.base.json sets no noUnusedLocals and the repo has no eslint config, so CI was green either way. That's the whole delta; it force-dismissed your approval, which is on me. Also dropped a stray tool-attribution footer from the PR description.

Verified beyond CI

  • The lockfile skew is inert at the artifact level, not just in principle. The built javascript/index.js contains 2.0.9's predicate: a => ssl === undefined || a.ssl === ssl, and sdk.host.getBridgeAddress resolves through the hoisted 2.0.9 — the nested 2.0.7 copies under bitcoin-core-startos and tor-startos never shadow it. The only symbols crossing that module boundary are rpcHostId and rpcPort, both scalars.
  • ssl: false is behavior-preserving on upgrade. Traced into the OS rather than inferred: bitcoin-core binds RPC protocol: 'http', and update_addresses emits the plaintext lxcbr0 entry with ssl: false because the bridge is is_intrinsically_secure(). So it resolves the same port net.assignedPort was returning, which is what the entrypoint's plain http:// curl wants. Identical on 28.x and the pinned next/28.x.
  • Matches the fleet. Every sibling PR in this pass that resolves bitcoind's RPC passes ssl: false; the P2P and ZMQ lookups all omit it. The doctrine page from #3560 uses bitcoind's RPC as the canonical two-address case, and its worked example is this call.
  • No restart loop. Watchable dedups on the resolved address string via eqFn before yielding, so .const() re-fires only when the address itself changes — same narrowing the old map-based sdk.host.get gave.
  • 0.9.6:7 is free (v0.9.6_6 is the latest tag, no competing PR), and neither registry has a package declaring utxoracle as a dependency, so there's no dependent versionRange to move.

One nit I left alone. The bump splits the SDK against the git dep, so the bundle goes 2.66 MB → 4.51 MB uncompressed. Compressed in the s9pk that's ~+336 KB on ~41.8 MB (+0.8%), against a bitcoind dependency measured in hundreds of GB. I left it because the house call is to fix skew at the source rather than pin an overrides. Worth knowing for sequencing, though: #237 targets 28.x, while this repo pins #next/28.x, which is still on 2.0.7 — so the dedupe collapses once next/28.x carries 2.0.9 and this repo re-locks, not when #237 merges. If you'd rather ship the smaller bundle now, "overrides": { "@start9labs/start-sdk": "2.0.9" } dedupes it and still typechecks and builds clean; say the word and I'll add it.

Merge and ordering are yours — merging publishes to the community registry, and this is one of 44 in the pass.

The docs still described the local helper this package no longer has, and
in places asserted its internals — reading `net.assignedPort`, "never
`addressInfo` hostnames" — which is the opposite of what
`sdk.host.getBridgeAddress` does. Also drops imports left dead by the
helper's removal.

@MattDHill MattDHill left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-approving after the doc-sync commit. Includes Helix's fixes on this branch, which correctly caught the dead sdk import and the stale AGENTS.md reference — both turned out to be fleet-wide and are now fixed everywhere.

@MattDHill
MattDHill merged commit 4f17757 into main Jul 25, 2026
3 checks passed
@MattDHill
MattDHill deleted the fix/bridge-address-resolution branch July 25, 2026 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants