fix(sweep): keep the UI sweep away from real wallet data
The full UI sweep (dev builds only) ran its demo surfaces against the
real profile. Its teardown deleted wallet-{cold,trading,mining,donations,
2023,payroll}.dat from the real datadir, it created zero-filled wallet
files and picker-demo/extra-wallets folders, its demo wallet-index upsert
re-minted the active wallet's scopeId (hiding "w:"-scoped contacts), the
Contacts tab could re-attach legacy-scoped contacts to such a throwaway
id in addressbook.json, and it persisted the demo active wallet file and
Mining pool worker.
- Preflight: a full sweep is refused (toast + log line) while a real
workflow is in flight: a pending seed_migration_* setting or open
migrate dialog, a wallet switch, an encryption/import workflow, an
encrypt/PIN/lock operation, the seed-backup dialog, or a bootstrap.
The DRAGONX_FULL_SWEEP hook then exits as for an empty catalog.
- Persistence latches: AddressBook (snapshot on the first sweepBegin,
save/load latched while a demo-contacts surface is shown), WalletIndex
and Settings (active wallet file, contacts view mode, pool URL/worker)
for the whole sweep. Settings::sweepEnd() is the last statement of
clearDemoWalletData and flushes once only if a save was suppressed; a
quit mid-sweep writes nothing to the profile (the 0700 sandbox, with
zero-filled demo files only, may stay in the temp dir).
- Scope ids: during a full sweep activeWalletScopeId() returns the
persisted id or "" and never mints one, so the Contacts tab cannot
move legacy-scoped contacts to a throwaway id. The only address-book
write a sweep can still cause is the one-time legacy re-attach to the
wallet's persisted id, which opening Contacts does anyway.
- Sandbox: demo wallet files and folders go to a fresh random 0700 dir
under the temp dir (exclusive create, a pre-existing path or symlink
is refused), removed at sweep end. The teardown that deleted those six
wallet files is gone.
- Cleanup: the migrate-to-seed state, wizard phase, demo chat
conversations and a fabricated demo chat identity are reset at sweep
end; the seed-backup surface always shows the demo phrase and a real
z_exportmnemonic result is dropped while the sweep runs.
- i18n: one new key (screenshot_sweep_refused) in i18n.cpp and the 8
language files.
Tests: the address book snapshots only on the first sweepBegin, keeps
addressbook.json byte-identical while latched and restores the real
book; the wallet index keeps wallets.json and the real scope id, also
with the background writer attached; Settings restores the latched
values and flushes once only when a save was suppressed; the sandbox
helper refuses a pre-existing path or symlink and removes only a
prefixed dir, never a symlink's target.
Release note: v2.0.x UI sweeps could delete wallet-{cold,trading,mining,
donations,2023,payroll}.dat from the data directory and re-scope
contacts; this cannot be undone automatically.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,13 @@ public:
|
||||
|
||||
static void hide() { s_open = false; }
|
||||
|
||||
// UI sweep ONLY: list and create wallets in this directory (the sweep's sandbox) instead of the real
|
||||
// datadir. Empty (the default) means the real datadir.
|
||||
static void setSweepDataDir(std::string dir) { s_sweepDataDir = std::move(dir); }
|
||||
static std::string dataDir() {
|
||||
return s_sweepDataDir.empty() ? util::Platform::getDragonXDataDir() : s_sweepDataDir;
|
||||
}
|
||||
|
||||
static void render() {
|
||||
if (!s_open || !s_app) return;
|
||||
// The folder picker (opened by "Scan another folder") takes over the modal surface while
|
||||
@@ -485,7 +492,7 @@ public:
|
||||
std::error_code ec;
|
||||
if (name.empty() || isLinkName(name)) { // reserve the in-place-link prefix
|
||||
Notifications::instance().warning(TR("wallets_name_invalid"));
|
||||
} else if (std::filesystem::exists(util::Platform::getDragonXDataDir() + "/" + name, ec)) {
|
||||
} else if (std::filesystem::exists(dataDir() + "/" + name, ec)) {
|
||||
Notifications::instance().warning(TR("wallets_exists"));
|
||||
} else {
|
||||
s_newName[0] = '\0';
|
||||
@@ -501,7 +508,7 @@ public:
|
||||
// Start in the DRAGONX data directory (where the datadir wallets live) so the user can
|
||||
// navigate up/out from a familiar anchor to find wallets in other folders.
|
||||
if (TactileButton(TR("wallets_scan_folder"), ImVec2(listW, 0))) {
|
||||
FolderPicker::open(util::Platform::getDragonXDataDir(), [app](const std::string& dir) {
|
||||
FolderPicker::open(dataDir(), [app](const std::string& dir) {
|
||||
std::error_code ec;
|
||||
if (!dir.empty() && std::filesystem::is_directory(dir, ec)) {
|
||||
if (app->walletIndex().addExtraFolder(dir)) app->walletIndex().save();
|
||||
@@ -577,7 +584,7 @@ public:
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("wallets_reveal"), ImVec2(150.0f * dp, 0)))
|
||||
util::Platform::openFolder(util::Platform::getDragonXDataDir());
|
||||
util::Platform::openFolder(dataDir());
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("close"), ImVec2(110.0f * dp, 0))) s_open = false;
|
||||
|
||||
@@ -680,7 +687,7 @@ private:
|
||||
static void openInPlace(App* app, const WalletRow& r) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
const std::string datadir = util::Platform::getDragonXDataDir();
|
||||
const std::string datadir = dataDir();
|
||||
fs::create_directories(datadir, ec);
|
||||
const std::string src = r.dir + "/" + r.fileName;
|
||||
// The row could be stale (file moved/deleted between the scan and this click). Don't link a ghost:
|
||||
@@ -834,7 +841,7 @@ private:
|
||||
s_scanning = false;
|
||||
|
||||
std::unordered_set<std::string> seen; // one row per physical wallet; the datadir is first, so it wins
|
||||
s_rows = util::scanDatadirWallets(util::Platform::getDragonXDataDir(), seen);
|
||||
s_rows = util::scanDatadirWallets(dataDir(), seen);
|
||||
s_probe = std::make_shared<ProbeBatch>();
|
||||
s_probe->results.resize(s_rows.size());
|
||||
startProbe(s_probe, 0); // datadir badges start filling in at once
|
||||
@@ -875,6 +882,7 @@ private:
|
||||
|
||||
static inline bool s_open = false;
|
||||
static inline App* s_app = nullptr;
|
||||
static inline std::string s_sweepDataDir; // UI sweep sandbox datadir ("" = the real one)
|
||||
static inline bool s_needScan = false;
|
||||
static inline bool s_forceScan = false; // next scan bypasses the folder-walk cache
|
||||
static inline bool s_scanning = false; // an external folder walk is in flight
|
||||
|
||||
Reference in New Issue
Block a user