feat(node): detect an unreadable block DB on startup and offer a one-click reindex
When a daemon update changes the block-index on-disk format (or the index is
corrupt), dragonxd aborts at startup — "non-canonical optional discriminant" →
"Error loading block database. Aborted." — and the wallet silently shows a zero
balance. Previously the connect loop just crash-restarted into the same abort up
to 3x and then reported a bare "Daemon crashed N times", with no path forward.
Now:
- daemon/daemon_startup_diagnosis.h: pure blockDbOutputLooksBroken() classifies
the crashed node's captured console output (the fatal block-DB markers).
- The connect loop detects it on the FIRST abort, STOPS crash-restarting into the
same failure (each retry reloads the whole index — wasteful), and offers a fix.
- A one-shot -reindex flag (EmbeddedDaemon::setReindexOnNextStart → DaemonController
forwarder → args) rebuilds the block index + chainstate from the intact raw
blocks; App::reindexBlockDatabase() arms it and un-gates the loop to restart.
- An auto-shown dialog (renderBlockDbReindexDialog) + a notification explain the
situation ("your coins are safe; the node just can't load the chain") and offer
a one-click "Rebuild block database". Full-node only (gated), lite-safe.
This is the exact trap behind a real "big wallet shows no funds" report: a
post-format-change daemon over pre-change chaindata. Reindex also fixes a plain
corrupt index.
Adds testBlockDbOutputDiagnosis (the abort sequence + individual markers trip it;
normal startup / wallet-corruption / asmap errors do not). Suite green (1/1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,11 @@ void DaemonController::setSalvageOnNextStart(bool enabled)
|
||||
daemon_->setSalvageOnNextStart(enabled);
|
||||
}
|
||||
|
||||
void DaemonController::setReindexOnNextStart(bool enabled)
|
||||
{
|
||||
daemon_->setReindexOnNextStart(enabled);
|
||||
}
|
||||
|
||||
bool DaemonController::zapOnNextStart() const
|
||||
{
|
||||
return daemon_->zapOnNextStart();
|
||||
|
||||
@@ -108,6 +108,7 @@ public:
|
||||
void setZapOnNextStart(bool enabled);
|
||||
bool zapOnNextStart() const;
|
||||
void setSalvageOnNextStart(bool enabled);
|
||||
void setReindexOnNextStart(bool enabled); // -reindex: rebuild the block DB from raw blocks on next start
|
||||
|
||||
static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon,
|
||||
bool externalDaemonDetected,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// daemon_startup_diagnosis.h — pure classifiers over a crashed daemon's captured console output,
|
||||
// so the app can offer a targeted one-click fix instead of a bare "daemon crashed" / silent no-funds.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
namespace daemon {
|
||||
|
||||
// True when dragonxd aborted because its BLOCK DATABASE could not be loaded — either a
|
||||
// daemon-vs-chaindata serialization-format mismatch after a daemon update (the deterministic
|
||||
// "non-canonical optional discriminant" → "Error loading block database" → "Aborted block database
|
||||
// rebuild. Exiting." sequence) or a genuinely corrupt/incomplete block index. In BOTH cases the fix
|
||||
// is the same: `-reindex` rebuilds the index + chainstate from the intact raw blocks (blk*.dat).
|
||||
// This is what otherwise silently presents as a wallet with zero balance — the node never starts.
|
||||
inline bool blockDbOutputLooksBroken(const std::string& out)
|
||||
{
|
||||
return out.find("Error loading block database") != std::string::npos
|
||||
|| out.find("non-canonical optional discriminant") != std::string::npos
|
||||
|| out.find("Aborted block database rebuild") != std::string::npos
|
||||
|| out.find("LoadBlockIndex()") != std::string::npos; // "... : failed to read value"
|
||||
}
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
@@ -571,6 +571,14 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
|
||||
args.push_back("-rescan");
|
||||
}
|
||||
|
||||
// -reindex rebuilds the block index + chainstate from the raw blocks (fixes an unreadable/format-
|
||||
// mismatched block DB). It's about the CHAIN, not the wallet, so it's independent of the wallet-repair
|
||||
// chain above (and implies its own wallet rescan). One-shot, consumed here.
|
||||
if (reindex_on_next_start_.exchange(false)) {
|
||||
DEBUG_LOGF("[INFO] Adding -reindex flag to rebuild the block database from raw blocks\n");
|
||||
args.push_back("-reindex");
|
||||
}
|
||||
|
||||
// One-shot isolated-datadir override (migrate-to-seed flow): run this start against a
|
||||
// throwaway datadir, plus any extra args (e.g. -connect=0). Consumed here so later starts
|
||||
// revert to the normal datadir. The datadir's basename MUST be the assetchain name (DRAGONX)
|
||||
|
||||
@@ -206,6 +206,13 @@ public:
|
||||
void setSalvageOnNextStart(bool v) { salvage_on_next_start_ = v; }
|
||||
bool salvageOnNextStart() const { return salvage_on_next_start_.load(); }
|
||||
|
||||
// -reindex: rebuild the block index + chainstate from the raw blocks (blk*.dat) on startup. One-shot,
|
||||
// consumed on the next start. Offered when the node aborts on an unreadable block database (a
|
||||
// daemon-vs-chaindata format mismatch after an update, or a corrupt index). It implies a wallet
|
||||
// rescan, so it's the block-DB analogue of -salvagewallet and coexists with the wallet-repair flags.
|
||||
void setReindexOnNextStart(bool v) { reindex_on_next_start_ = v; }
|
||||
bool reindexOnNextStart() const { return reindex_on_next_start_.load(); }
|
||||
|
||||
/**
|
||||
* @brief One-shot isolated-datadir override for the NEXT start(): run the daemon against a
|
||||
* different datadir (with its own DRAGONX.conf) plus the given extra args. Used by the
|
||||
@@ -306,6 +313,7 @@ private:
|
||||
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
||||
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
||||
std::atomic<bool> salvage_on_next_start_{false}; // -salvagewallet flag for next start
|
||||
std::atomic<bool> reindex_on_next_start_{false}; // -reindex flag for next start (rebuild block DB)
|
||||
std::string override_datadir_; // one-shot: -datadir for the next start
|
||||
std::vector<std::string> override_extra_args_; // one-shot: extra args for the next start
|
||||
bool skip_port_check_ = false; // isolated instance on a non-default port
|
||||
|
||||
Reference in New Issue
Block a user