fix(audit): batch 1 — latent unlock/path/rescan correctness bugs
Four correctness fixes surfaced by the codebase audit: 1. Lite address-book path bug: AddressBook::getDefaultPath() hardcoded "ObsidianDragon" while Settings::getDefaultPath() uses DRAGONX_APP_NAME, so the Lite build wrote addressbook.json into the full-node app's config dir instead of ObsidianDragonLite/. Now uses DRAGONX_APP_NAME on all platforms (no macOS path change — the getConfigDir consolidation, which would move the macOS dir, is deferred). 2. PIN-unlock lockout bypass: the PIN path duplicated unlockWallet's success/lockout logic and its RPC-error branch bumped the attempt counter but skipped the escalating-lockout math entirely — a PIN user hitting an RPC error escaped the lockout curve. Extracted App::applyUnlockSuccess() and App::applyUnlockFailure() and routed all unlock paths (passphrase, PIN vault-fail, PIN RPC-fail) through them, so the lockout curve now applies uniformly. (noVault stays a mode-switch, not a failed attempt.) 3. Witness/rescan reset drift: the ~11-line rescan+witness completion reset was copy-pasted at four sites (app.cpp x2, app_network.cpp x2); adding a witness field and missing a copy would leave stale progress. Folded into App::resetWitnessRescanProgress(). Left the distinct phase-transition reset in App::update() untouched (it sets witness_phase, not 0). 4. Truncation underflow: send_tab/receive_tab's size_t TruncateAddress copies computed (maxLen - 3) without guarding maxLen <= 3, which wraps and throws std::out_of_range on short inputs. Added the guard. Full-node + Lite build clean; ctest 1/1; source-hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -924,6 +924,24 @@ void App::applyPendingSendBalanceDeltas(bool includeAggregateBalances)
|
||||
}
|
||||
}
|
||||
|
||||
void App::resetWitnessRescanProgress()
|
||||
{
|
||||
// Common core of the four rescan-completion sites. Deliberately does NOT set
|
||||
// rescan_progress (callers set it to 1.0 only on success) or runtime_rescan_active_
|
||||
// (only the manual-rescan path uses it), and is distinct from the witness phase-transition
|
||||
// reset in App::update() which sets witness_phase to the new phase rather than clearing it.
|
||||
rescan_confirmed_active_ = false;
|
||||
state_.sync.rescanning = false;
|
||||
state_.sync.rescan_status.clear();
|
||||
state_.sync.building_witnesses = false;
|
||||
state_.sync.witness_phase = 0;
|
||||
state_.sync.witness_progress = 0.0f;
|
||||
state_.sync.witness_remaining = 0;
|
||||
witness_rebuild_total_blocks_ = 0;
|
||||
witness_seen_txids_.clear();
|
||||
witness_total_txs_ = 0;
|
||||
}
|
||||
|
||||
std::string App::transactionHistoryCacheWalletIdentity() const
|
||||
{
|
||||
std::vector<std::string> shieldedAddresses;
|
||||
@@ -1156,17 +1174,8 @@ void App::refreshCoreData()
|
||||
// rescan_confirmed_active_ ensures we actually observed this rescan running (set
|
||||
// by the getrescaninfo / daemon-log pollers) before declaring it done.
|
||||
if (state_.sync.rescanning && rescan_confirmed_active_) {
|
||||
state_.sync.rescanning = false;
|
||||
rescan_confirmed_active_ = false;
|
||||
resetWitnessRescanProgress();
|
||||
state_.sync.rescan_progress = 1.0f;
|
||||
state_.sync.rescan_status.clear();
|
||||
state_.sync.building_witnesses = false;
|
||||
state_.sync.witness_phase = 0;
|
||||
state_.sync.witness_progress = 0.0f;
|
||||
state_.sync.witness_remaining = 0;
|
||||
witness_rebuild_total_blocks_ = 0;
|
||||
witness_seen_txids_.clear();
|
||||
witness_total_txs_ = 0;
|
||||
// Notes/witnesses were rebuilt — force a fresh history + balance pull.
|
||||
transactions_dirty_ = true;
|
||||
last_tx_block_height_ = -1;
|
||||
@@ -2652,16 +2661,7 @@ void App::runtimeRescan(int startHeight)
|
||||
}
|
||||
return [this, ok, err]() {
|
||||
runtime_rescan_active_ = false;
|
||||
rescan_confirmed_active_ = false;
|
||||
state_.sync.rescanning = false;
|
||||
state_.sync.rescan_status.clear();
|
||||
state_.sync.building_witnesses = false;
|
||||
state_.sync.witness_phase = 0;
|
||||
state_.sync.witness_progress = 0.0f;
|
||||
state_.sync.witness_remaining = 0;
|
||||
witness_rebuild_total_blocks_ = 0;
|
||||
witness_seen_txids_.clear();
|
||||
witness_total_txs_ = 0;
|
||||
resetWitnessRescanProgress();
|
||||
if (ok) {
|
||||
state_.sync.rescan_progress = 1.0f;
|
||||
transactions_dirty_ = true;
|
||||
|
||||
Reference in New Issue
Block a user