fix: Tier-2 UX robustness — action guards, in-progress guards, input trimming

The app-wide "import-key pattern" fixes (missing guards / untrimmed input / no
re-entrancy protection), from the robustness audit:

- Import-key dialog: unify the type indicator and the RPC dispatch on one
  classifier (classifyPrivateKey is now prefix-aware — an "SK..." shielded key no
  longer misroutes to the transparent RPC), trim manual input (not just Paste),
  and gate both the indicator and the Import button on a shared
  isRecognizedPrivateKey() so unrecognized/empty input can't be submitted.
- Shield: guard the submit button on connected + !syncing (like Send), with a
  disabled tooltip, instead of firing into a raw daemon error.
- Mining: disable the pool Mine button when the payout address is empty
  ("enter a payout address first"), and trim the pool URL/worker on persist.
- Console: track in-flight RPC commands (atomic counter + busy() override) so the
  input is disabled while a command runs instead of piling up on the worker.
- Maintenance (rescan/repair/delete-chain/reinstall-daemon): re-entrancy guard —
  bail with "already in progress" instead of launching a duplicate destructive op.
- Bootstrap dialog: re-attach to an in-flight download on reopen instead of
  resetting state and orphaning the running worker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 13:09:23 -05:00
parent df14533ad3
commit 129a8e6449
10 changed files with 122 additions and 28 deletions

View File

@@ -97,7 +97,22 @@ WalletSecurityController::KeyKind WalletSecurityController::classifyAddress(cons
WalletSecurityController::KeyKind WalletSecurityController::classifyPrivateKey(const std::string& key)
{
return !key.empty() && key[0] == 's' ? KeyKind::Shielded : KeyKind::Transparent;
// Shielded: Sapling extended spending key (secret-extended-key-...) or legacy Sprout (SK...).
// (The old `key[0]=='s'` test misrouted an uppercase "SK..." shielded key to the transparent RPC.)
if (key.rfind("secret-extended-key-", 0) == 0) return KeyKind::Shielded;
if (key.size() >= 2 && key[0] == 'S' && key[1] == 'K') return KeyKind::Shielded;
if (!key.empty() && key[0] == 's') return KeyKind::Shielded;
return KeyKind::Transparent;
}
bool WalletSecurityController::isRecognizedPrivateKey(const std::string& key)
{
if (key.rfind("secret-extended-key-", 0) == 0) return true; // Sapling z spending key
if (key.size() >= 2 && key[0] == 'S' && key[1] == 'K') return true; // Sprout z spending key
// Transparent WIF: base58, ~51-52 chars, common version prefixes.
if (key.size() >= 51 && key.size() <= 52 &&
(key[0] == '5' || key[0] == 'K' || key[0] == 'L' || key[0] == 'U')) return true;
return false;
}
const char* WalletSecurityController::importSuccessMessage(KeyKind kind)