Skip to content

audit: sdk 2.0, disk guardrails, and dependency-contract fixes - #1

Merged
MattDHill merged 2 commits into
masterfrom
sdk-2.0-audit
Aug 3, 2026
Merged

audit: sdk 2.0, disk guardrails, and dependency-contract fixes#1
MattDHill merged 2 commits into
masterfrom
sdk-2.0-audit

Conversation

@helix-nine

Copy link
Copy Markdown

Community-submission review of Brendan's (@brendio) Elements (Liquid) package, rebuilt on start-sdk 2.0.9. Version 23.3.3:023.3.3:1.

Land this before the PeerSwap PR — PeerSwap's dependency gate and address resolution both assume these changes.

The disk story

The submitted docs told users to "plan for a few GB and growing (far smaller than Bitcoin mainnet)". Sampling 900 blocks across 60 evenly-spaced points of the chain (tip 3,997,188) puts it at ~85 GB of raw block data today:

Chain quarter Mean block size Segment total
Q1 (0 → 1M) 14.8 KB ~15 GB
Q2 (1M → 2M) 6.8 KB ~7 GB
Q3 (2M → 3M) 6.8 KB ~7 GB
Q4 (3M → tip) 55.4 KB ~55 GB

Growth is steeply non-linear — recent sample points average 100–235 KB/block, i.e. 150–330 MB/day, or 50–120 GB/year and rising. The package already sets validatepegin=0, which correctly removes the full-Bitcoin-node requirement; the Liquid chain itself is the remaining cost.

hardwareRequirements has no disk field, so the guardrails are in JS, following bitcoin-core as the fleet reference:

  • txindex was enforced on. It is not needed — PeerSwap resolves swap transactions with getrawtransaction <txid> <verbose> <blockhash> over a bounded block range (txwatcher/blockchainRpc.go), never by bare txid — and it forbids pruning. Now a user toggle defaulting off, which is also upstream's default.
  • prune exposed, disk-aware, defaulting to a pruned target on hosts below the archival threshold and clamped to half the disk. Safe with a wide margin: PeerSwap's Liquid CSV window is 60 blocks (onchain/liquid.go), so even the 550 MiB floor holds days of history against a one-hour requirement.
  • disk-space health checkfailure below a few GB free, where elementsd risks corrupting chainstate, plus a notification once per service start while merely low.
  • hardwareRequirements.ram gates install; dbcache now derives from host RAM.

Correctness

  • SubContainer.of is lazy in SDK 2.0, so .rootfs is a Promise. The cookie path interpolated [object Promise] into both the daemon ready-check and the stale-cookie removal. Template literals accept any value, so tsc was silent. → SubContainer.eager.
  • alerts manifest block — removed in SDK 2.0, retained silently because setupManifest's generic inference bypasses excess-property checking.
  • listen=1 was enforced but the P2P port was never bound, leaving a dead peerPort constant. Now bound and exported as a p2p interface.
  • runtime-info advertised elements.startos — the retired DNS form. It now reports the resolved bridge address, and rpcHostId / peerHostId are exported so dependents can use sdk.host.getBridgeAddress.

Conformity

Vendored s9pk.mk (three SDK releases stale) deleted in favour of the SDK's; tsconfig.json on the 2.0 form; CI repointed from the retired start9labs/shared-workflows to Start9Labs/start-technologies with the tagAndRelease push trigger restored; AGENTS.md synced to the current package template; the as Record<string, LangDict> cast replaced with the satisfies form that actually enforces per-locale completeness (new strings added across all five locales); as string casts and a duplicated temp subcontainer removed from runtime-info; README/instructions/ABOUT rewritten against writing-readmes.md / writing-instructions.md.

Test plan

Built x86_64 on start-sdk 2.0.9 and installed on StartOS 0.4.0.1.

  1. make x86 install, then start the service.
  2. Health checks — all three report: RPC green ("The Liquid RPC interface is ready"), Liquid Sync loading with a percentage, Disk Space green with real free space (662.3 GB free on the test box, confirming diskusage.check('/') reads the data pool and not a container overlay).
  3. Config action — run config with prune set to a non-zero value. Confirm elements.conf gains prune=<n> and txindex=0 (the two are mutually exclusive in Elements; formToFile forces it), and that the enforced keys are intact. Confirm the daemon logs Unsetting NODE_NETWORK on prune mode / Pruning blockstore… on the next start.
  4. Disk-aware boundsstart-cli package action get-input elements config and confirm prune's max is half the disk and txindex's disabled reflects disk size.
  5. Interfaces — confirm both bindings exist: rpc on 7041 and the new peer on 7042.
  6. Runtime info — run runtime-info and confirm it returns live chain data plus a resolved bridge address (10.0.3.1:7041 on the test box), not elements.startos.
  7. Wallet — confirm the peerswap wallet is created on first run and pinned with load_on_startup=true.

Not verified: a full Liquid sync (~85 GB, many hours — the test node reached ~12%), and aarch64 locally; CI covers the build matrix.

🤖 Generated with Claude Code

Rebuilds the package on start-sdk 2.0.9 and works through the community
submission review.

SDK 2.0:
- drop the `alerts` manifest block, removed in 2.0. It survived silently
  because setupManifest's generic inference bypasses excess-property checking,
  so tsc never flagged it
- `SubContainer.eager` in main.ts. `SubContainer.of` is lazy in 2.0, so
  `.rootfs` is a Promise, and the cookie path was interpolating
  "[object Promise]" into both the daemon ready-check and the stale-cookie
  removal. Template literals accept any value, so this compiled clean
- consume `s9pk.mk` and `tsconfig.base.json` from the SDK rather than the
  vendored copies, which had drifted three releases behind
- repoint CI at Start9Labs/start-technologies (start9labs/shared-workflows is
  retired) and restore the push trigger on tagAndRelease

Disk footprint. The Liquid sidechain is past 80 GB of raw block data and
growing tens of GB a year — sampling 900 blocks across the chain puts the last
quarter alone at ~55 GB, with recent blocks an order of magnitude larger than
mid-history ones. The docs described this as "a few GB". hardwareRequirements
has no disk field, so the guardrails are in JS, following bitcoin-core:

- `txindex` was enforced on. It is not needed: PeerSwap resolves swap
  transactions with `getrawtransaction <txid> <verbose> <blockhash>` over a
  bounded block range (txwatcher/blockchainRpc.go), never by bare txid. It also
  forbids pruning. It is now a user toggle defaulting off, matching upstream
- expose `prune`, disk-aware, defaulting to a pruned target on hosts too small
  for an archival node. PeerSwap's Liquid CSV window is 60 blocks, so even the
  550 MiB floor retains orders of magnitude more history than any consumer reads
- add a `disk-space` health check: fails below a few GB free, where elementsd
  risks corrupting its chainstate, and notifies once per service start while
  merely low
- `hardwareRequirements.ram` gates install on hosts too small to run the daemon
- `dbcache` now derives its default from host RAM rather than a fixed 450 MiB

Dependency contract:
- export `rpcHostId` / `peerHostId` so dependents resolve the address with
  `sdk.host.getBridgeAddress` instead of the retired `elements.startos:7041`
  DNS form; `runtime-info` reports the resolved bridge address too
- bind and export the P2P port, which `listen=1` already implied but nothing
  exposed, retiring a dead `peerPort` constant

Also: retire the `as Record<string, LangDict>` cast for the `satisfies` form
that actually enforces per-locale completeness, add the new strings across all
five locales, drop the `as string` casts and the duplicated temp subcontainer in
`runtime-info`, sync AGENTS.md to the current package template, and rewrite
README/instructions/ABOUT against the packaging guide's checklists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
StartOS compares hardwareRequirements.ram against the host's RAM in bytes
(start-core init.rs stores MemTotal * 1024 * 1024; HardwareRequirements::
is_compatible does a raw < comparison). A literal 4096 therefore declared a
4 KiB floor and gated nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill merged commit e54f96b into master Aug 3, 2026
3 checks passed
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