feat(ui): surface daemon DEGRADED mode + v1.3.0 auto-shield status
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>
This commit is contained in:
+39
-13
@@ -249,6 +249,21 @@ void App::detectWalletAutoRecovery()
|
||||
VERBOSE_LOGF("[recovery] Daemon auto-recovered/salvaged wallet.dat — surfacing the recovery dialog\n");
|
||||
}
|
||||
|
||||
// v1.3.0+ opens a wallet that lost its hdchain in DEGRADED mode (funds spendable) instead of aborting,
|
||||
// but can't derive NEW HD keys — z_getnewaddress / z_shieldcoinbase / t->z z_sendmany fail with "HD seed
|
||||
// not found". Only a startup log line signals it, so scan the captured output and warn once. Pre-1.3.0
|
||||
// daemons never emit it, so this is a no-op there (backwards compatible).
|
||||
void App::detectWalletDegraded()
|
||||
{
|
||||
if (wallet_degraded_warned_) return;
|
||||
if (!isUsingEmbeddedDaemon() || !daemon_controller_ || !daemon_controller_->daemon()) return;
|
||||
if (!daemon::walletOpenedDegraded(daemon_controller_->daemon()->getOutput())) return;
|
||||
wallet_degraded_ = true;
|
||||
wallet_degraded_warned_ = true;
|
||||
ui::Notifications::instance().warning(TR("wallet_degraded_notify"), 30.0f);
|
||||
VERBOSE_LOGF("[recovery] Daemon opened wallet in DEGRADED mode — new-key derivation disabled\n");
|
||||
}
|
||||
|
||||
void App::tryConnect()
|
||||
{
|
||||
// Lite builds have no full node / RPC daemon, so never run the RPC connection state machine
|
||||
@@ -259,6 +274,7 @@ void App::tryConnect()
|
||||
// Catch a startup wallet salvage as soon as it appears in the node's output — independent of whether
|
||||
// the node ever finishes starting or connects (skip only while an orchestrated swap is mid-flight).
|
||||
if (!daemon_restarting_) detectWalletAutoRecovery();
|
||||
if (!daemon_restarting_) detectWalletDegraded();
|
||||
|
||||
if (connection_in_progress_) return;
|
||||
|
||||
@@ -1746,29 +1762,39 @@ void App::refreshCoreData()
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-shield transparent funds if enabled. A v1.3.0+ daemon auto-shields coinbase
|
||||
// itself; probe z_autoshieldstatus once (while synced, so the daemon is past warmup) and
|
||||
// defer to it when it's active — otherwise the wallet and the daemon race for the same
|
||||
// coinbase UTXOs and split funds across different z-addresses. A pre-1.3.0 daemon lacks
|
||||
// the RPC, so the probe fails closed (active=false) and the wallet keeps shielding.
|
||||
const bool autoShieldEligible = result.balanceOk && settings_ && settings_->getAutoShield() &&
|
||||
state_.spendableTransparentBalance > 0.0001 && !state_.sync.syncing;
|
||||
if (autoShieldEligible && !daemon_autoshield_probed_ && worker_ &&
|
||||
// Probe the daemon's auto-shield status once per connection — independent of our own
|
||||
// toggle/balance — so both the defer-gate below and the Settings UI (O1) can read it.
|
||||
// Only when synced (daemon past warmup). Fail-closed: a pre-1.3.0 daemon lacks the RPC,
|
||||
// so the probe leaves active=false and the wallet keeps shielding client-side.
|
||||
if (result.balanceOk && !state_.sync.syncing && !daemon_autoshield_probed_ && worker_ &&
|
||||
!daemon_autoshield_probe_inflight_.exchange(true)) {
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
bool active = false;
|
||||
bool active = false, seedRecoverable = false;
|
||||
std::string addr, reason;
|
||||
try {
|
||||
auto st = rpc_->call("z_autoshieldstatus", json::array());
|
||||
if (st.is_object() && st.contains("autoshield"))
|
||||
active = st["autoshield"].get<bool>();
|
||||
} catch (...) { active = false; } // pre-1.3.0 daemon: method not found
|
||||
return [this, active]() {
|
||||
if (st.is_object()) {
|
||||
if (st.contains("autoshield")) active = st["autoshield"].get<bool>();
|
||||
if (st.contains("autoshieldaddress")) addr = st["autoshieldaddress"].get<std::string>();
|
||||
if (st.contains("disabled_reason")) reason = st["disabled_reason"].get<std::string>();
|
||||
if (st.contains("seed_recoverable")) seedRecoverable = st["seed_recoverable"].get<bool>();
|
||||
}
|
||||
} catch (...) {} // pre-1.3.0 daemon: no such method — leave defaults (inactive)
|
||||
return [this, active, addr, reason, seedRecoverable]() {
|
||||
daemon_autoshield_active_ = active;
|
||||
daemon_autoshield_address_ = addr;
|
||||
daemon_autoshield_disabled_reason_ = reason;
|
||||
daemon_autoshield_seed_recoverable_ = seedRecoverable;
|
||||
daemon_autoshield_probed_ = true;
|
||||
daemon_autoshield_probe_inflight_ = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-shield transparent funds — but defer to the daemon's own coinbase auto-shielder
|
||||
// (v1.3.0+) when it's active, so we don't double-shield and split funds across z-addrs.
|
||||
const bool autoShieldEligible = result.balanceOk && settings_ && settings_->getAutoShield() &&
|
||||
state_.spendableTransparentBalance > 0.0001 && !state_.sync.syncing;
|
||||
if (autoShieldEligible && daemon_autoshield_probed_ && !daemon_autoshield_active_ &&
|
||||
!auto_shield_pending_.exchange(true)) {
|
||||
std::string targetZAddr;
|
||||
|
||||
Reference in New Issue
Block a user