fix: large-wallet sync starvation + shutdown/console-flash UX (Windows full node) #2

Closed
DanS wants to merge 0 commits from fix/balance-poll-sync-contention into dev
Owner

Summary

Fixes found while diagnosing a Windows full-node wallet that appeared stuck "downloading blocks" on a large (~5.3k-tx) fully-shielded wallet. Root cause was not the network — the wallet was starving the daemon's single block-connect thread. Five commits:

  1. 8707934 sync starvation — the Overview balance poll (z_gettotalbalance, O(mapWallet) under cs_main, ~20s on this wallet) fired every ~2s and held cs_main, so ActivateBestChain/ConnectBlock only ran in the gaps and the node fell further behind than it caught up. Fix: adaptive balance cadence (next poll waits >= scan-cost / 10%) + sync-settle hysteresis so it can't re-starve near tip; wallet mutations still force an immediate balance refresh.
  2. 29274c2 startup/shutdown UX — trim the verbose "taking longer than expected" notice; the shutdown screen now tails debug.log (windowless) for external daemons so the node flush/exit is visible.
  3. 7e8b99a shutdown safety — confirm before stopping the daemon mid Sapling-witness-rebuild (stopping discards it -> multi-minute redo next launch): Keep-running / Stop-anyway / Cancel.
  4. a2f84be console-flash on launch — spawn dragonxd with CREATE_NO_WINDOW instead of CREATE_NEW_CONSOLE.
  5. 0942691 windowless shell-outs — route the remaining GPU/xmrig/wallet-rebuild _popen calls through a CreateProcess+CREATE_NO_WINDOW helper (Platform::runHiddenCapture) so they never flash cmd.exe.

Testing

  • Linux build + test_phase4 green on every commit; Windows cross-compile (--win-release) verified per commit.
  • Root cause live-verified on a running mainnet node: z_gettotalbalance/z_listunspent ~20s each holding cs_main; one core pegged on GetFilteredNotes; block-connect thread in Wait; download idle (~17 B/s) with all peers at max in-flight.

Out of scope (separate, unvalidated)

A daemon-side fix to persist witness-rebuild progress across an interrupted rescan lives in the external/dragonx repo on branch fix/witness-rebuild-checkpoint. It is not compiled or tested (fund-adjacent) and must pass a node build + regtest/testnet/mainnet validation + adversarial review before shipping — intentionally excluded from this PR.

🤖 Generated with Claude Code

## Summary Fixes found while diagnosing a Windows full-node wallet that appeared stuck "downloading blocks" on a large (~5.3k-tx) fully-shielded wallet. Root cause was **not** the network — the wallet was starving the daemon's single block-connect thread. Five commits: 1. **`8707934` sync starvation** — the Overview balance poll (`z_gettotalbalance`, O(mapWallet) under `cs_main`, ~20s on this wallet) fired every ~2s and held `cs_main`, so `ActivateBestChain`/`ConnectBlock` only ran in the gaps and the node fell further behind than it caught up. Fix: adaptive balance cadence (next poll waits >= scan-cost / 10%) + sync-settle hysteresis so it can't re-starve near tip; wallet mutations still force an immediate balance refresh. 2. **`29274c2` startup/shutdown UX** — trim the verbose "taking longer than expected" notice; the shutdown screen now tails `debug.log` (windowless) for external daemons so the node flush/exit is visible. 3. **`7e8b99a` shutdown safety** — confirm before stopping the daemon mid Sapling-witness-rebuild (stopping discards it -> multi-minute redo next launch): Keep-running / Stop-anyway / Cancel. 4. **`a2f84be` console-flash on launch** — spawn `dragonxd` with `CREATE_NO_WINDOW` instead of `CREATE_NEW_CONSOLE`. 5. **`0942691` windowless shell-outs** — route the remaining GPU/xmrig/wallet-rebuild `_popen` calls through a `CreateProcess`+`CREATE_NO_WINDOW` helper (`Platform::runHiddenCapture`) so they never flash `cmd.exe`. ## Testing - Linux build + `test_phase4` green on every commit; Windows cross-compile (`--win-release`) verified per commit. - Root cause live-verified on a running mainnet node: `z_gettotalbalance`/`z_listunspent` ~20s each holding `cs_main`; one core pegged on `GetFilteredNotes`; block-connect thread in Wait; download idle (~17 B/s) with all peers at max in-flight. ## Out of scope (separate, unvalidated) A daemon-side fix to persist witness-rebuild progress across an interrupted rescan lives in the `external/dragonx` repo on branch `fix/witness-rebuild-checkpoint`. It is **not compiled or tested** (fund-adjacent) and must pass a node build + regtest/testnet/mainnet validation + adversarial review before shipping — intentionally excluded from this PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
DanS added 12 commits 2026-08-31 00:02:42 -05:00
Send and Receive are now consistent in layout, and the Send recipient row's buttons
render correctly.

Card envelope (Send ⇄ Receive consistency):
- Add Layout::mainComposeCardBox(availW) — a single shared source for the compose card's
  width + centering (fill the available column up to content-max-width, then center). Both
  tabs derive their card from it, so they can't drift again. Previously Send capped at
  760dp and Receive at 860dp, so the Send card rendered ~150px narrower on any window wider
  than ~860dp; now they fill available width identically.

Receive:
- Justify the footer buttons edge-to-edge (equal shares over the live count) instead of
  left-clustering with dead space, matching Send's full-width footer rhythm.
- Build the address-dropdown preview to the combo's real pixel width so the trailing
  balance ("— 12.00000000 DRGX") no longer hard-clips at 150% (was char-count truncation).

Send recipient row (input | Paste | contacts-icon):
- Pin the contacts icon button to the frame height so the larger iconMed font doesn't
  auto-size it taller than Paste/the input.
- Reserve the real ItemSpacing.x gaps (not the smaller spacingSm token) so the row no
  longer overshoots the card and clips the icon's right border.

draw_helpers (root cause, app-wide):
- TactileButton's icon path measured/drew the label INCLUDING the "##id" suffix (which
  CalcTextSizeA/AddText don't strip the way ImGui's text render does), shoving the glyph
  off-center-left. Strip at "##" before measuring/drawing. Corrects any icon button that
  passes an explicit size and a "##id" label; no-op for labels without "##".

Verified via headless sweeps at 1.0x and 1.5x, plus a real 3800px-wide render (both cards
byte-identical at L=1174/R=2773). ctest 1/1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings tabs brought closer to the approved mockup:
- ActionButton/renderCardButton retune (settings-scoped): 7px radius, 9px
  padX, Primary → accent-outline chip, Secondary/card buttons more defined.
- Daemon-binary card: compact status right-aligned on the DAEMON BINARY
  heading (Up to date / Version differs / Not installed), filled/rounded
  status box, neutral danger divider (was alarming red), roomier spacing.
- RPC Connection: two-row column-aligned layout (Host | Port, then
  Username | Password) so the password no longer clips off the card edge.
- Chat settings tab: live conversation preview below the Appearance /
  Messaging cards; "Focus input on open" checkbox reflowed onto the console
  color-toggle row.
- Debug Options: "Current theme only" toggle restricts either screenshot
  sweep to the active theme instead of cycling every skin.
- Tabs fill the full content width (content-max-width cap disabled) and the
  sidebar nav panel centers within the true visible area.
- i18n: new keys for the above (untranslated keys fall back to English).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ensureLogoTexture() rasterized the embedded DragonX SVG into logo_tex_ and
returned early (added in 1752500 "themed DragonX logo"), so the app/product
branding — the top-left header (app.cpp AddImage) and the About tab
(getLogoTexture) — showed the DragonX coin mark instead of the ObsidianDragon
logo. Drop that step so logo_tex_ resolves via the intended path: active-skin
override → ui.toml header-icon → bundled ObsidianDragon dark/light PNG (disk,
then embedded RESOURCE_LOGO). The DragonX SVG stays for coin_logo_tex_ (balance
card) and drgx_emoji_tex_ (chat emoji), which are the currency mark and correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add "The DragonX Developers" to the About-tab credits (after The Hush
Developers), acknowledging the DragonX chain/daemon this wallet drives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The BDB wallet.dat bloats with shielded-note witness data and never shrinks
in place, so a mining/shielded wallet can grow past 500 MB. Below the Wallet
Size row, show a one-line amber hint once wallet.dat crosses 500 MB with a
"Consolidate notes…" shortcut that opens the Merge to Address (z_mergetoaddress)
dialog. Full-node only (lite has no wallet.dat here); threshold is a single
named constant. i18n keys fall back to English for non-English locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the wallet-bloat warning beyond the Settings banner: when wallet.dat
first crosses 500 MB (full-node, synced), fire a one-time warning toast plus a
clickable "Consolidate notes…" entry in the bell/alerts panel that opens Merge
to Address. The persisted large_wallet_warned flag keeps it once-only and
re-arms if the file later shrinks back under the threshold.

- AlertRecord gains an optional onClick + actionHint; Notifications::action()
  pushes a toast and a clickable history entry. renderAlertHistoryPanel() now
  renders the accent action link (under the message) and measures true content
  height so wrapped messages + the link aren't clipped.
- App::maybeWarnLargeWallet() (mirrors maybeRemindSeedBackup) runs once per
  launch from update(); reuses the existing wallet_size_warn/consolidate strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make Merge to Address actually serve wallet-bloat consolidation and be far less
opaque. New ShieldDialog::showConsolidate() preset (used by the large-wallet
Settings banner + alert action) frames it as "Consolidate funds" and targets
shielded notes — the bloat the nudge warns about.

- Source selector: consolidate shielded notes (ANY_SAPLING), transparent
  (ANY_TADDR), or both (*) — previously hardcoded to ANY_TADDR, which never
  reduced the shielded-witness bloat. Batch limit now applies to the right side.
- Scope: on open, count spendable UTXOs + notes (listunspent / z_listunspent)
  and show "N transparent + M shielded · ~X DRGX"; warn "repeat to finish" when
  the set exceeds one batch.
- Destination auto-selects the best spendable z-address (button enabled by
  default); empty wallets get an inline "Create shielded address" (z_getnewaddress).
- Advanced disclosure hides Fee + "Max inputs per batch" (renamed from the "UTXO
  Limit" jargon) with sane defaults.
- Inline confirm step before the fund-moving call (amount + input count + dest).
- Live progress: self-polls z_getoperationstatus to show Consolidating… →
  Done/Failed, replacing the raw opid + manual "Check status" button.

All three merge entry points now use the typed showMerge()/showConsolidate()
(no stale-static leaks from direct show(MergeToAddress)). Shield-coinbase mode
keeps working. New i18n keys fall back to English.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a fully-shielded (ac_private=1) chain, z_gettotalbalance is O(mapWallet) and
holds the daemon's cs_main for its whole duration — ~20s on a ~5k-tx wallet. The
Overview refresh polled it every ~2s (twice: minconf 0 and 1), so cs_main was
held almost continuously, starving the single block-connection thread: the node
connected blocks only in the gaps between polls and could fall further behind
the tip than it caught up (observed live: gap growing 58→100 blocks while the
GUI was open, one core pegged on GetFilteredNotes, 22 idle, ~17 B/s download).

Two hardening changes on top of the existing "skip balance while syncing" guard:

- Hysteresis: keep the low-impact sync profile (and balance suppression) for a
  short settle window after catching up, so a large-wallet scan can't
  immediately re-starve connection and bounce the node back into syncing. Armed
  only on the syncing→caught-up edge, so a wallet synced from the start is never
  throttled at connect (effectivelySyncing()).

- Adaptive balance cadence: time each z_gettotalbalance scan and require the
  next poll to wait at least (cost / 10%), so balance scanning never occupies
  more than ~10% of wall-clock. Cheap wallets are unaffected (the tab's Core
  timer stays the cadence); a ~20s scan backs off to ~200s. Wallet mutations
  (send/shield) force the next poll through so the user's own action updates the
  balance immediately (balanceRefreshDue()).

getblockchaininfo keeps its normal cadence throughout, so sync progress stays
live. Build + test_phase4 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Loading "taking longer than expected" notice: shorten the body + hint so the
  startup screen reads less wordy (same info, ~half the text).
- Shutdown screen: when the wallet attached to an EXTERNAL daemon (no captured
  stdout — debug_log_path_ is only set when we spawn it), the "dragonxd output"
  panel was always empty, leaving just a spinner. Fall back to tailing the
  daemon's debug.log so the user can watch the node flush the block index and
  exit. Adds App::tailDaemonDebugLog() (best-effort, reads only the file tail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stopping dragonxd while it's rebuilding the Sapling witness cache discards the
in-progress work — BuildWitnessCache aborts on shutdown without persisting — so
the next launch redoes a multi-minute rebuild (the "Activating best chain…" hang).
This bites especially with stop_external_daemon enabled, where wallet exit sends
the node a stop.

beginShutdown() now defers when it would StopDaemon while a rebuild is active and
shows a confirm modal: "Keep node running & quit" (DisconnectOnly — leaves it up
to finish), "Stop anyway & quit", or "Cancel". Rebuild detection reads the
debug.log tail markers (Cleared witness data / Setting Initial Sapling Witness /
Reading blocks for witness rebuild, vs. the "rebuilt … in …ms" / abort lines).
The gate lives entirely in beginShutdown()/render() — no SDL event-loop changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The embedded daemon was launched with CREATE_NEW_CONSOLE + SW_HIDE. CREATE_NEW_CONSOLE
allocates a console window that flashes on screen before SW_HIDE hides it — visible as a
console-window flash every time the wallet starts dragonxd (i.e. on launch). Switch to
CREATE_NO_WINDOW (the console child gets no window at all, matching the xmrig launcher);
dragonxd logs to debug.log, not a console, so nothing is lost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_popen/_popen-style shell-outs flash a cmd.exe console window on Windows. Add
Platform::runHiddenCapture() — CreateProcess + CREATE_NO_WINDOW capturing stdout on
Windows, popen on POSIX — and route the remaining shell-outs through it:
- GPU-aware idle detection (getGpuUtilization: "where nvidia-smi" / "nvidia-smi --query-gpu")
- xmrig discovery + version (findXmrigBinary "where xmrig.exe"; "<bin> --version", stderr merged)
- wallet-rebuild helper (app_network) — keeps its exit-code check via the new exitCode out-param

None of these are on the launch path (that was the daemon spawn, fixed in a2f84be); each
would flash a console only when it ran (idle-GPU mining, mining tab, wallet recovery).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-08-31 22:39:43 -05:00
v1.3.0 daemons auto-shield coinbase by default (when the HD seed is recoverable — which
every ObsidianDragon-created wallet is, via -usemnemonic=1). The wallet also ran its own
client-side auto-shield every refresh tick, so both raced for the same coinbase UTXOs and
split funds across different z-addresses (the wallet picks the first z_listaddresses entry;
the daemon uses a seed-hardened derivation).

Probe z_autoshieldstatus once per connection (while synced, so the daemon is past warmup)
and skip the wallet's client-side shield when the daemon reports it active. Fail-closed: a
pre-1.3.0 daemon has no such RPC, so the probe returns active=false and the wallet keeps
shielding — no regression on the currently-bundled v1.0.3. Re-probes on reconnect (handles
a live daemon upgrade/swap).

Follow-up (not done): drive the Settings "Auto-shield" toggle + disabled_reason from
z_autoshieldstatus (O1) — needs runtime verification of the RPC fields on a live v1.3.0 node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 2 commits 2026-08-31 22:50:51 -05:00
Replace the wallet's stale hardcoded -addnode list (node. + node1-4.dragonx.is — a drifted
subset with a bogus bare 'node.') with the daemon's authoritative vSeeds: seed.dragonx.is (a
round-robin A record over the live seed set, so it self-updates without a wallet release) plus
node1/node5 as static fallbacks. Applied in both seeding sites: the launch args
(embedded_daemon.cpp) and the generated DRAGONX.conf (connection.cpp).

Kept (not deleted) — pre-1.3.0 daemons had broken peer discovery and rely on these -addnode
entries to find peers at all; plain hostname resolution works on every daemon version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rec3 — v1.3.0+ opens a wallet that lost its hdchain in DEGRADED mode (existing funds spendable,
but no new HD-key derivation) instead of aborting. Add a daemon-log classifier
(walletOpenedDegraded) + detectWalletDegraded(), warned once per session. Pre-1.3.0 daemons
never emit that line, so it's a no-op there.

O1 — probe z_autoshieldstatus once per connection (now decoupled from our own toggle/balance) and,
in Settings, show whether the node handles auto-shield itself (+ its destination, or the daemon's
disabled_reason). The checkbox now governs only the wallet's fallback shielder, which defers to the
node. Nothing renders on pre-1.3.0 daemons (no such RPC), so behaviour is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-08-31 23:21:46 -05:00
Add an opt-in "Host a mining pool (stratum)" toggle so a v1.3.0+ node can run its native
RandomX stratum server for other miners to point at. Settings toggle + optional allow-IP/CIDR;
passes -stratum (+ -stratumallowip) to the daemon launch args, mirroring the -maxconnections
plumbing (EmbeddedDaemon::setStratumHosting <- DaemonController::syncSettings <- Settings).

Backwards compatible / safe by default:
- UI gated on daemon_version >= 1030000, so it's never offered where it would do nothing.
- The launch flag is harmless on older daemons (they ignore unknown args), and the toggle can
  only be enabled while connected to a v1.3.0+ node anyway.
- Blank allow-IP => the daemon serves loopback only (its safe default); entering a subnet opens
  it to that LAN, with an explicit exposure warning in the UI.
- Takes effect on the next daemon start/restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 00:16:42 -05:00
It added clutter to the loading screen (the yellow title + two lines of explanatory text).
The live daemon-output panel below it is the real progress signal. connect_stall_since_ stays
maintained in app_network.cpp for connection bookkeeping; it just no longer drives any on-screen
text (loading_stall_* i18n strings + util::connectHasStalled are now unused).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 00:50:56 -05:00
While minimized the main loop skips the frame body (SDL_Delay+continue), so App::update() —
which drains the daemon output via outputSince(daemon_output_offset_) — never runs and the
offset isn't advanced. On restore the whole accumulated backlog is parsed in one batch: a
background witness rebuild's progress lines (parsed as a rescan → state_.sync.rescanning=true)
AND its "rebuilt … in Xms" completion (parsed as finished) arrive together and fire
"Blockchain rescan complete" for a scan the user never initiated.

On WINDOW_RESTORED, discard the daemon-output backlog (advance the offset to the current end,
via App::skipDaemonOutputBacklog) before the resumed update parses it — so only new output is
parsed. Genuine user-initiated rescans still surface completion via the getrescaninfo monitor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 01:10:49 -05:00
Previously the main loop just did SDL_Delay+continue when minimized, skipping app.update() —
so the wallet stopped draining RPC results, ticking the refresh scheduler, and reconnecting
until it was restored (and a large daemon-output backlog piled up, which is what produced the
spurious "rescan complete" toast on restore).

Now app.update() runs while minimized (it only reads GetIO/GetTime/IsAnyItemActive, all valid
outside a NewFrame) with a real-clock DeltaTime, skipping only the ImGui frame + GPU present,
throttled to ~5 Hz so CPU stays near-idle. Also clamp io.DeltaTime at the top of App::update()
so a long minimize (or machine sleep) can't report a huge delta and fire every refresh/animation
timer at once on the next update. No backlog now builds, so skipDaemonOutputBacklog becomes a
harmless no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 01:20:39 -05:00
The daemon-output parser treats autonomous background witness rebuilds as a rescan (they set
state_.sync.rescanning via the foundWitness branch), so one completing fired "Blockchain rescan
complete" even though the user never started a rescan — most visibly after a minimize, where a
whole rebuild's start+finish arrives in one batch.

Add user_initiated_rescan_ (atomic — some triggers run on worker threads), set it at the wallet's
real rescan triggers (the Rescan button, a -rescan/salvage/zap/reindex restart, key import, seed
migration), and gate the three "rescan complete" toasts on it, clearing it when shown. The
rescan/witness progress state machine is untouched — only the toast is gated — so background
rebuilds no longer announce a completed rescan while genuine user rescans still do (and can't
double-toast).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 02:22:49 -05:00
The shutdown guard fired its "Node is rebuilding its witness cache" prompt almost always on an
active wallet: the daemon does a per-tx VerifyAndSetInitialWitness as each newly received wallet
tx lands during normal sync, and those markers (sparse — minutes apart — with no matching
"rebuilt N note witness cache(s) … in Xms") kept the heuristic latched "active".

- daemonWitnessRebuildActive() now requires the last progress marker to be part of the CURRENT
  log activity (within ~15s of the newest log line, via a same-log timestamp delta), so routine
  minutes-old per-tx witness sets no longer count as an ongoing rebuild.
- shouldConfirmDaemonStop() suppresses the prompt entirely on v1.3.0+ daemons (version >= 1030000):
  they checkpoint witness-rescan progress, so stopping mid-rebuild resumes on the next start rather
  than redoing it — the warning's "restarts it (several minutes)" premise no longer holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-01 23:42:54 -05:00
- Auto-shield status: the node's z_autoshieldstatus disabled_reason was echoed
  verbatim ("HD seed origin is not known-recoverable; back the seed up and pass
  -autoshield=1") and drawn INSIDE the Wallet OPTIONS checkbox grid, wedging a
  full-width line between the checkboxes. Move it to a full-width note BELOW the
  grid, and replace the raw daemon text with friendly, actionable wording keyed
  on the seed_recoverable flag (back up your seed to enable it); the raw daemon
  reason is kept on hover. Adds App::daemonAutoShieldSeedRecoverable().
- FAQ: add a Chat & Contacts entry ("How do I hide, delete, or block a
  conversation?") covering the hide / delete-revive / delete-&-block actions and
  the local-only caveat — data + i18n only, no UI code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS added 1 commit 2026-09-02 02:29:37 -05:00
The macOS release path hardcoded aarch64-apple-darwin for the wallet-rebuild
helper's static libdb and — unlike the Linux/Windows paths — never passed
-DBDB_INCLUDE_DIR/-DBDB_LIBRARY to CMake. So on an Intel Mac the helper either
couldn't find libdb, or picked up macOS's SDK stub /usr/include/db.h (a DB 1.85
shim with no db_create) and failed to compile.

- mac_bdb_dir(): map the target arch to the depends triple (x86_64 ->
  x86_64-apple-darwin, arm64 -> aarch64-apple-darwin; universal / unknown falls
  back to the host arch).
- Pass -DBDB_INCLUDE_DIR / -DBDB_LIBRARY (when the vendored libdb is present) in
  BOTH the native and osxcross configures, so CMake uses our BDB 6.2 header
  instead of the SDK stub. Full-node only (lite has no BDB wallet.dat).
- Use the derived triple in require_wallet_rebuild_helper's fix hint.

Verified end-to-end by building both ObsidianDragon 2.0.1 and ObsidianDragonLite
1.1.0 .dmg/.app on an Intel Mac (x86_64; built single-arch via DRAGONX_MAC_ARCHS
since the vendored deps are x86_64-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DanS closed this pull request 2026-10-19 19:24:23 -05:00
DanS deleted branch fix/balance-poll-sync-contention 2026-10-19 19:24:23 -05:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DragonX/ObsidianDragon#2