docs: current-state the bitcoin multi-version design; move its rollout handoff local
bitcoin-multi-version-design.md carried three layers of stale internal content: an 80-line HTML-comment work-tracking block (per-phase status with "UNCOMMITTED on the branch", node numbers, "Next action when resuming", "Decisions still needed from user"); a rendered "Status: design (2026-06-22)" header that was wrong — the feature shipped, all four phases, with the downgrade guard added today; two private `MEMORY →` references; and a node-numbered scheduling note. Now: the comment block is gone, the status reflects reality, the MEMORY references and node numbers are removed, and "verify on a real node" replaced the specific fleet addresses. The design content (source-of-truth decision, phase designs, invariants) is unchanged. Separately, bitcoin-version-bulletproof-rollout.md was an inter-agent rollout handoff — node numbers, branch coordination, "the other agent owns" — not a design or reference doc. Moved to local-only (still on disk, gitignored) like the other handoffs; its two path references (a plan doc and a script comment) are generalized. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b4e4189407
commit
a2efdf7358
@@ -141,3 +141,4 @@ uploads/
|
||||
/docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md
|
||||
/image-recipe/INTEGRATION-GUIDE.md
|
||||
/docs/multinode-testing-plan.md
|
||||
/docs/bitcoin-version-bulletproof-rollout.md
|
||||
|
||||
@@ -391,7 +391,7 @@ media (latest artifact only one minor behind).
|
||||
**`1.8.0-alpha`**. Remaining work is the mechanical bump + `create-release.sh` run
|
||||
when the gate criteria are met.
|
||||
- [ ] 🟢 **Bitcoin multi-version fleet OTA** — DECIDED (user, 2026-07-08): timing doesn't
|
||||
matter; fold the branch into the next fleet OTA (`docs/bitcoin-version-bulletproof-rollout.md`).
|
||||
matter; fold the branch into the next fleet OTA.
|
||||
- [x] ~~⛔🟢 **3ccc stock-Meshtastic RF validation**~~ — DROPPED per user 2026-07-08; the
|
||||
code fix stays in, no live-radio validation will be scheduled.
|
||||
|
||||
|
||||
@@ -1,91 +1,7 @@
|
||||
# Bitcoin Multi-Version Support — Design
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════════════
|
||||
PROGRESS TRACKER / RESUME POINT (keep this current — update each session)
|
||||
════════════════════════════════════════════════════════════════════
|
||||
**Branch/worktree:** `bitcoin-multi-version` @ `/home/archipelago/Projects/archy-btcver`
|
||||
(isolated — never touch `main` or the other agent's branch). All work UNCOMMITTED on
|
||||
that branch as of last update.
|
||||
|
||||
**Last updated:** 2026-06-28 (session 2 — software end-to-end implemented)
|
||||
|
||||
**Motivation refresh:** BIP-110 signalling makes per-node version *choice* a real
|
||||
requirement — runners must be able to pick / pin / switch Core & Knots versions.
|
||||
|
||||
**User direction this session:** finish the SOFTWARE end-to-end (Phase 1–3 + UI),
|
||||
DEFER the Phase 0 image build pipeline. Downgrade policy = **warn + confirm + allow**.
|
||||
|
||||
### Status by phase
|
||||
- [x] **Phase 1 — catalog schema** (`app_catalog.rs`): `CatalogVersion` struct +
|
||||
`versions[]` + `catalog_versions()` / `catalog_default_version()` /
|
||||
`catalog_image_for_version()` (same-repo guard) DONE. Pin suppresses update badge
|
||||
in `available_update_for_app()` DONE. `versions[]` now EMITTED by
|
||||
`scripts/generate-app-catalog.sh` (curated `VERSIONS` map) → `releases/app-catalog.json`
|
||||
regenerated; bitcoin-core carries its one built version (28.4.0, default). **Knots
|
||||
versions[] intentionally empty** (only floating `:latest` exists; design forbids
|
||||
advertising floating). More versions light up automatically once Phase 0 builds
|
||||
tagged images and they're appended to the `VERSIONS` map.
|
||||
- [x] **Phase 2 — install-time selection**: `version_config.rs` (pin/auto-update
|
||||
persistence + `is_downgrade()` + `auto_update_apps()`, unit-tested) DONE;
|
||||
`install.rs` `persist_install_version_selection()` DONE; `prod_orchestrator.rs`
|
||||
pinned-wins resolution DONE. **UI:** `MarketplaceAppDetails.vue` install panel shows
|
||||
a version `<select>` (latest pre-selected) when the app offers ≥2 versions — passes
|
||||
the choice to `package.install`. (Hidden today since only 1 version exists.)
|
||||
- [x] **Phase 3 — in-app switch + auto-update toggle**:
|
||||
- `package.versions` RPC (read) + `package.set-config` RPC (write, downgrade-gated)
|
||||
→ new `api/rpc/package/set_config.rs`, wired in `mod.rs` + `dispatcher.rs`.
|
||||
- Auto-update tick: `run_update_scheduler` now takes the orchestrator + calls
|
||||
`apply_per_app_auto_updates()` hourly (opt-in, pin-respecting, catalog-driven).
|
||||
- UI: "Version & Updates" card in `appDetails/AppSidebar.vue` (version switch +
|
||||
auto-update toggle + downgrade warn/confirm); `rpc-client.ts` + types added.
|
||||
- [x] **Phase 0 — image build pipeline**: `scripts/build-bitcoin-image.sh` —
|
||||
downloads the OFFICIAL upstream tarball + SHA256SUMS(.asc), verifies SHA-256 **and**
|
||||
the OpenPGP signature (fail-closed; pinned release-key fingerprints), builds a
|
||||
minimal **rootless** image (debian-slim + verified `bitcoind`/`bitcoin-cli`),
|
||||
smoke-tests `--version`, tags + pushes `:<version>`. Validated on Core 31.0
|
||||
(pinned-GPG pass, smoke `v31.0.0`). **Published curated set** (registry
|
||||
`lfg2025`): Core **31.0, 30.2, 29.3, 27.2, 26.2, 25.2** (28.4 already present —
|
||||
kept, not overwritten) + Knots **29.3.knots20260508**. `VERSIONS` map in
|
||||
`generate-app-catalog.sh` lists them; catalog regenerated. Adding a future release
|
||||
= run the script for it, then prepend it to the map + regenerate.
|
||||
|
||||
### Verification status
|
||||
- `cargo check -p archipelago` GREEN (backend). Frontend `npm run build` GREEN
|
||||
(vue-tsc typecheck passes; new RPC strings confirmed in `web/dist`).
|
||||
- Unit tests: `version_config` had a pre-existing parallel-test race (shared
|
||||
process-global `ARCHIPELAGO_DATA_DIR`) — FIXED with an `ENV_LOCK` mutex + unique
|
||||
per-test dirs. `set_config` `image_tag` test added.
|
||||
- **Phase 0 images verified end-to-end**: SHA-256 + pinned-maintainer OpenPGP
|
||||
signature (deterministic VALIDSIG check), built rootless, smoke-tested, **pushed
|
||||
to the live registry** — confirmed remotely: `bitcoin` tags
|
||||
{25.2,26.2,27.2,28.4,29.3,30.2,31.0} + `bitcoin-knots:29.3.knots20260508`.
|
||||
- **NOT yet verified on `.228`** (CLAUDE.md invariant — do before any tag): install
|
||||
bitcoin-core, open its page, switch/pin a version, confirm recreate. All code
|
||||
UNCOMMITTED on the branch.
|
||||
|
||||
### Gotchas captured (for resume)
|
||||
- `gpg --verify` exit code is unreliable on multi-sig `SHA256SUMS` — must parse
|
||||
`--status-fd` VALIDSIG and require a pinned maintainer fpr (script does this).
|
||||
- `podman push` needs the sandbox disabled (`/var/tmp` is RO under the harness
|
||||
sandbox) and `--tls-verify=false` (registry serves HTTP). Persistent keyring
|
||||
(`BITCOIN_KEYRING_DIR`) avoids flaky per-build keyserver fetches.
|
||||
|
||||
### Next action when resuming
|
||||
1. Re-verify: `cd archy-btcver/core && CARGO_INCREMENTAL=0 cargo check -p archipelago`
|
||||
and `cargo test -p archipelago -- version_config set_config`; `cd neode-ui && npm run build`.
|
||||
2. Live-verify on `.228`: install bitcoin-core, open its detail page → "Version &
|
||||
Updates" card; exercise `package.versions` / `package.set-config` via RPC.
|
||||
3. Commit on the branch (checkpoint).
|
||||
4. **Phase 0** when greenlit: build+push tagged Core/Knots images, then extend the
|
||||
`VERSIONS` map in `scripts/generate-app-catalog.sh` and regenerate the catalog.
|
||||
|
||||
### Decisions still needed from user (see §6 open questions)
|
||||
Curated version set + storage budget (defaulted to current+~3 majors); when to do
|
||||
Phase 0 image pipeline; pruned-node downgrade policy refinement (currently warn+confirm
|
||||
for all). Auto-update default = OFF (opt-in), as recommended.
|
||||
════════════════════════════════════════════════════════════════════ -->
|
||||
|
||||
**Status:** design (2026-06-22)
|
||||
**Status:** implemented — all four phases shipped (catalog schema, install-time selection, in-app switch + auto-update toggle, and the verified image build pipeline). Downgrades are guarded: the update path never offers a lower version than what is running.
|
||||
**Goal:** let a user choose *which* version of Bitcoin Core / Bitcoin Knots to
|
||||
install (latest pre-selected, older versions in a dropdown), and later switch
|
||||
versions or opt into auto-update — all manifest/catalog-driven, all served from
|
||||
@@ -94,13 +10,8 @@ changes.
|
||||
|
||||
See also: [`docs/registry-manifest-design.md`](registry-manifest-design.md)
|
||||
(catalog distribution + signing this builds on),
|
||||
the production test gate (which must be
|
||||
green first), `MEMORY → project_decoupled_app_updates`,
|
||||
`MEMORY → project_manifest_driven_north_star`.
|
||||
and the production test gate (which must be green first).
|
||||
|
||||
> **Scheduling:** this is net-new scope. It lands **after** the production test
|
||||
> gate (`tests/lifecycle/run-20x.sh`) is green on `.228` + `.198`. The data-
|
||||
> preservation invariant (downgrade vs. chainstate) is the highest risk here.
|
||||
|
||||
---
|
||||
|
||||
@@ -260,7 +171,7 @@ free. `version`/`image` stay as the default for back-compat.
|
||||
- **Floating tags** (`latest`) are never advertised as a selectable "version" and
|
||||
never counted as an available update (already handled by
|
||||
`available_update_for_app`).
|
||||
- **Verify on a real node** (`.228` then `.198`) and pass `run-20x` before any
|
||||
- **Verify on a real node** and pass the lifecycle gate before any
|
||||
tag.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# Bitcoin Multi-Version — Bulletproofing & Rollout (handoff)
|
||||
|
||||
> **Status 2026-06-29:** code + images + catalog + frontend DONE on branch
|
||||
> `bitcoin-version-bulletproof` (base commit `095a76cd`, plus the catalog-generator
|
||||
> + handoff follow-ups). **.228 is the test node**: binary + frontend + catalog are
|
||||
> live there; its Knots chainstate is mid-**reindex recovery** (see §5). The fleet
|
||||
> rollout (OTA binary+frontend, mirror catalog publish, `:latest` repoint) is the
|
||||
> **coordinated step the other agent owns** — see §4. Pairs with
|
||||
> `docs/bitcoin-multi-version-design.md` (the original design).
|
||||
|
||||
## 1. What was broken (root causes)
|
||||
|
||||
User report: "switched Knots to `v29.3.knots20260508`, version didn't update in the UI."
|
||||
Three **stacked** bugs, plus a data-corruption hazard:
|
||||
|
||||
1. **Reconciler reverted the pin.** `prod_orchestrator::sync_quadlet_unit` re-rendered the
|
||||
quadlet every reconcile tick using the manifest's `:latest`, ignoring the per-app
|
||||
pinned version → any switch silently reverted within one tick.
|
||||
2. **Entrypoint render bug.** The renderer folded the manifest `entrypoint: ["sh","-lc"]`
|
||||
into `Exec=`. That only works when the image ENTRYPOINT is a passthrough shell wrapper.
|
||||
The versioned images use `ENTRYPOINT ["bitcoind"]`, so `Exec=sh -lc …` became
|
||||
`bitcoind sh -lc …` → `unexpected token 'sh'` → crash loop.
|
||||
3. **Image USER divergence.** The versioned images were built `USER bitcoin` (uid 1000);
|
||||
the legacy `:latest` ran as **root**. Chain data is owned by the `data_uid`
|
||||
(host 100101 / container uid 102). Root reads it via `CAP_DAC_OVERRIDE` (granted in the
|
||||
manifest); uid-1000 cannot → `Error initializing block database`.
|
||||
4. **Data hazard (already hit on .228).** Repeated failed starts under mixed UIDs left
|
||||
bitcoind's two LevelDBs (`blocks/index/` + `chainstate/`) truncated to KB stubs while
|
||||
the raw `blocks/blk*.dat` (797 GB) stayed intact. Recovery = `bitcoind -reindex` from
|
||||
local blocks (no re-download). The uniform-root image fix (below) removes the mixed-UID
|
||||
cause going forward; the proper switch flow was already data-safe (600s stop grace,
|
||||
clean stop→rm→recreate, conflict-stops the other impl — they share port 8332 + datadir
|
||||
`/var/lib/archipelago/bitcoin`).
|
||||
|
||||
## 2. What was fixed (all on the branch)
|
||||
|
||||
- **Renderer** (`core/archipelago/src/container/`):
|
||||
- `prod_orchestrator.rs`: factored `resolve_catalog_image()` (catalog/pinned-version →
|
||||
image) and call it in BOTH `install_fresh` and `sync_quadlet_unit` — the pin now
|
||||
survives reconcile.
|
||||
- `quadlet.rs`: emit a real `Entrypoint=<first>` + `Exec=<rest+cmd>` instead of folding;
|
||||
`exec_changed` now also diffs `Entrypoint=` so the recreate fires. Validated against
|
||||
the live podman 5.4.2 quadlet generator.
|
||||
- **Images** (`scripts/build-bitcoin-image.sh`, `apps/bitcoin-{knots,core}/Dockerfile`):
|
||||
removed `USER bitcoin` → run as **container-root** like legacy (still 100% rootless:
|
||||
container-root maps to the unprivileged host service user; `CAP_DAC_OVERRIDE` from the
|
||||
manifest lets bitcoind read the `data_uid`-owned datadir). **All** images rebuilt root +
|
||||
pushed to the mirror (`source.archipelago-foundation.org/lfg2025`):
|
||||
- Knots: `29.3.knots20260508`, `29.3.knots20260507`, `29.3.knots20260210`, `29.2.knots20251110`
|
||||
- Core: `25.2 26.2 27.2 28.4 29.2 29.3 30.2 31.0` + `latest` (→31.0)
|
||||
- **Catalog** (`scripts/generate-app-catalog.sh` VERSIONS map + regenerated
|
||||
`releases/app-catalog.json`): Knots & Core `versions[]` populated; the generator now
|
||||
forces top-level `version` == the `default` entry's version (the `169ff2e2` invariant)
|
||||
regardless of the manifest version. Knots `latest` entry points at the newest **dated**
|
||||
image (`29.3.knots20260508`) so "Always use latest" = newest on fixed-binary nodes.
|
||||
- **Frontend** (`neode-ui/`):
|
||||
- `AppSidebar.vue`: rename the latest option to **"Always use the latest version"**
|
||||
(no `v` prefix), fix right padding, and `pickSelection()` guarantees the bound value is
|
||||
a real option (fixes the blank dropdown).
|
||||
- New `components/InstallVersionModal.vue`: full-screen version chooser shown from the
|
||||
App Store / Discover **card** install button for multi-version apps — app icon +
|
||||
"Install <name>", latest pre-selected. Wired in `Discover.vue handleInstall`.
|
||||
- i18n keys: `appDetails.alwaysUseLatestVersion`, `marketplace.installModalTitle/Hint`.
|
||||
|
||||
## 3. Current live state on .228 (test node)
|
||||
|
||||
- Binary with both renderer fixes: **deployed** (`/usr/local/bin/archipelago`).
|
||||
- New frontend bundle: **deployed** to `/opt/archipelago/web-ui` (hard-refresh to see it).
|
||||
- Updated catalog: placed at `/var/lib/archipelago/app-catalog.json` (local override —
|
||||
will refresh from the mirror's OLDER copy at the next hourly fetch until §4 publishes it).
|
||||
- Knots: `bitcoin-knots` service held **stopped** (`package.stop`, user_stopped);
|
||||
a detached `bitcoin-knots-reindex` container is rebuilding the index+UTXO (§5).
|
||||
|
||||
## 4. Remaining — coordinated fleet rollout (OTHER AGENT)
|
||||
|
||||
Do this together with the other workstream's release, AFTER both are ready:
|
||||
|
||||
1. **Merge** branch `bitcoin-version-bulletproof` into the release line.
|
||||
2. **Build + OTA** the binary + frontend (these carry the renderer fix + UI). The renderer
|
||||
fix is a **hard prerequisite** for the new images everywhere — see fleet-safety below.
|
||||
3. **Publish the catalog** to the mirror (push `releases/app-catalog.json` to gitea-vps2
|
||||
`main`, the raw URL nodes fetch hourly). The current catalog is **fleet-safe even before
|
||||
the binary lands**: unpinned/auto-update nodes resolve via the manifest's floating
|
||||
`:latest` (still the legacy image); only explicit version selection (needs the new UI)
|
||||
uses the new root images.
|
||||
4. **Only AFTER the binary is fleet-wide:** optionally repoint the `bitcoin-knots:latest`
|
||||
tag → `29.3.knots20260508` (root) and simplify the catalog `latest` entry back to the
|
||||
`:latest` tag. **Do NOT repoint `:latest` before then** — old-binary nodes fold
|
||||
`Exec=sh -lc …` and would crash on an `ENTRYPOINT ["bitcoind"]` image. (Core never
|
||||
worked on old binaries — it always shipped `ENTRYPOINT ["bitcoind"]` — so Core has no
|
||||
such constraint.)
|
||||
5. **Verify the full switch matrix** on a healthy node (§6).
|
||||
|
||||
## 5. Finishing .228's reindex (OTHER AGENT owns this — not babysat by the original author)
|
||||
|
||||
The detached `bitcoin-knots-reindex` container runs the new **root** `29.3.knots20260508`
|
||||
image with `-reindex -server=0` against `/var/lib/archipelago/bitcoin`. It holds the datadir
|
||||
lock, so the managed service (held stopped) can't collide. When it has connected blocks up
|
||||
to ~the prior tip (height ≥ ~955800) it's done; then:
|
||||
|
||||
```sh
|
||||
# on .228 (SSH/sudo/UI pw all: <FLEET_PW>)
|
||||
podman stop -t 600 bitcoin-knots-reindex && podman rm bitcoin-knots-reindex
|
||||
# start the managed service via RPC (sets desired=running, clears user_stopped):
|
||||
# package.start {id: bitcoin-knots} (POST https://127.0.0.1/rpc/v1, CSRF: echo csrf_token cookie as X-CSRF-Token)
|
||||
# verify:
|
||||
podman exec bitcoin-knots sh -lc '$(command -v bitcoind) --version | head -1' # → v29.3.knots20260508
|
||||
# RPC up → the Bitcoin UI populates; it syncs the gap to tip.
|
||||
```
|
||||
The "Bitcoin RPC connection refused (127.0.0.1:8332)" the UI shows is EXPECTED until this
|
||||
swap (reindex runs with RPC off).
|
||||
|
||||
## 6. Switch-matrix test plan (what "bulletproof" must prove)
|
||||
|
||||
On a healthy node, each step must end with bitcoind running + RPC answering + syncing, with
|
||||
NO `Error initializing block database` and NO data loss:
|
||||
- Knots: switch `latest` → `29.3.knots20260507` → `29.3.knots20260210` → back to `latest`.
|
||||
- Core: install `latest`; switch `31.0` → `28.4.0`.
|
||||
- **Knots ↔ Core** (shared datadir/port): Knots→Core upgrade path (Core ≥ data version) and
|
||||
the reverse. **Cross-major DOWNGRADES** (e.g. 29.x data → Core 28.4) legitimately need a
|
||||
reindex — the UI already surfaces a downgrade warning; confirm it does and that confirming
|
||||
reindexes cleanly rather than crash-looping.
|
||||
- Reboot survival after each switch.
|
||||
|
||||
## 7. Notes / assumptions
|
||||
|
||||
- **"29.2"** in the request doesn't exist as a Knots build (404 upstream); added as **Bitcoin
|
||||
Core 29.2** (exists). Revisit if a Knots 29.2 was meant.
|
||||
- Reindex is unavoidable ONLY because .228's index was already corrupted by the pre-fix
|
||||
crash loop; a normal switch on the fixed binary does NOT reindex.
|
||||
- Creds for .228: SSH/sudo + UI/RPC all `<FLEET_PW>`.
|
||||
@@ -203,7 +203,7 @@ VERSIONS = {
|
||||
# newest build on fixed-binary nodes, while UNPINNED nodes still resolve via
|
||||
# the manifest's floating :latest tag (kept on the legacy image until the
|
||||
# entrypoint-render fix is fleet-deployed — see
|
||||
# docs/bitcoin-version-bulletproof-rollout.md).
|
||||
# the bitcoin multi-version design).
|
||||
"bitcoin-knots": [
|
||||
{"version": "latest",
|
||||
"image": f"{REGISTRY}/bitcoin-knots:29.3.knots20260508", "default": True},
|
||||
|
||||
Reference in New Issue
Block a user