feat(lite): real backend integration — controller, M0-M2a wiring, smoke tool, tests

- LiteWalletController (src/wallet/lite_wallet_controller.*): App-owned; runs real
  create/open/restore via the linked SDXL bridge with allowBridgeCalls=true; wipes
  seed/passphrase with sodium_memzero; persists on a ready wallet. M2a:
  applyLiteRefreshModelToWalletState maps a parsed refresh bundle into WalletState
  (zatoshi->DRGX, z/t split, tx typing + confirmations, sync progress).
- App wiring: liteWallet() accessor + init() construction when supportsLiteBackend();
  persist -> settings save.
- settings_page: "Validate" reroutes to the controller for real execution (validation-
  only fallback otherwise); wipes UI secret buffers after submit.
- chain name default -> "main" with load-time migration of legacy "DRAGONX"
  (settings.cpp), preventing the backend "Unknown chain" panic.
- M0: build.sh --lite-backend flag; lite_smoke real-backend tool + CMake targets;
  tests/fake_lite_backend.h deterministic harness.
- Tests (test_phase4): injectable-fake bridge, controller lifecycle, chain-name
  migration, refresh->WalletState mapping; plus the lite test-suite churn-cleanup rewrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 21:15:44 -05:00
parent 863d015628
commit 5586f334a4
12 changed files with 3597 additions and 121 deletions

View File

@@ -30,6 +30,33 @@ namespace config {
Settings::Settings() = default;
Settings::~Settings() = default;
namespace {
Settings::LiteServerSelectionPreferenceMode parseLiteServerSelectionPreferenceMode(
const json& value)
{
if (!value.is_string()) return Settings::LiteServerSelectionPreferenceMode::Sticky;
const std::string mode = value.get<std::string>();
if (mode == "random" || mode == "Random") {
return Settings::LiteServerSelectionPreferenceMode::Random;
}
return Settings::LiteServerSelectionPreferenceMode::Sticky;
}
const char* liteServerSelectionPreferenceModeName(
Settings::LiteServerSelectionPreferenceMode mode)
{
switch (mode) {
case Settings::LiteServerSelectionPreferenceMode::Sticky:
return "sticky";
case Settings::LiteServerSelectionPreferenceMode::Random:
return "random";
}
return "sticky";
}
} // namespace
std::string Settings::getDefaultPath()
{
#ifdef _WIN32
@@ -148,6 +175,54 @@ bool Settings::load(const std::string& path)
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
const auto& lite = j["lite_wallet"];
if (lite.contains("server_selection_mode")) {
lite_server_selection_mode_ = parseLiteServerSelectionPreferenceMode(
lite["server_selection_mode"]);
}
if (lite.contains("sticky_server_url") && lite["sticky_server_url"].is_string()) {
lite_sticky_server_url_ = lite["sticky_server_url"].get<std::string>();
}
if (lite.contains("chain_name") && lite["chain_name"].is_string()) {
lite_chain_name_ = lite["chain_name"].get<std::string>();
}
// Migration: the SDXL backend only accepts main/test/regtest and hard-panics
// (process abort) on any other chain name. Older builds persisted the "DRAGONX"
// ticker here, which crashed the lite backend on launch. Rewrite any invalid
// value to "main" and flag a re-save so the corrected setting persists.
if (lite_chain_name_ != "main" && lite_chain_name_ != "test" &&
lite_chain_name_ != "regtest") {
lite_chain_name_ = "main";
needs_upgrade_save_ = true;
}
if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_unsigned()) {
lite_random_selection_seed_ = lite["random_selection_seed"].get<std::size_t>();
} else if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_integer()) {
const auto seed = lite["random_selection_seed"].get<long long>();
lite_random_selection_seed_ = seed > 0 ? static_cast<std::size_t>(seed) : 0;
}
if (lite.contains("persist_selected_server") && lite["persist_selected_server"].is_boolean()) {
lite_persist_selected_server_ = lite["persist_selected_server"].get<bool>();
}
if (lite.contains("servers") && lite["servers"].is_array()) {
lite_servers_.clear();
for (const auto& server : lite["servers"]) {
if (!server.is_object()) continue;
LiteServerPreference preference;
if (server.contains("url") && server["url"].is_string()) {
preference.url = server["url"].get<std::string>();
}
if (server.contains("label") && server["label"].is_string()) {
preference.label = server["label"].get<std::string>();
}
if (server.contains("enabled") && server["enabled"].is_boolean()) {
preference.enabled = server["enabled"].get<bool>();
}
lite_servers_.push_back(preference);
}
}
}
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
debug_categories_.clear();
@@ -265,6 +340,23 @@ bool Settings::save(const std::string& path)
j["keep_daemon_running"] = keep_daemon_running_;
j["stop_external_daemon"] = stop_external_daemon_;
j["max_connections"] = max_connections_;
{
json lite = json::object();
lite["server_selection_mode"] = liteServerSelectionPreferenceModeName(lite_server_selection_mode_);
lite["sticky_server_url"] = lite_sticky_server_url_;
lite["chain_name"] = lite_chain_name_;
lite["random_selection_seed"] = lite_random_selection_seed_;
lite["persist_selected_server"] = lite_persist_selected_server_;
lite["servers"] = json::array();
for (const auto& server : lite_servers_) {
json entry = json::object();
entry["url"] = server.url;
entry["label"] = server.label;
entry["enabled"] = server.enabled;
lite["servers"].push_back(entry);
}
j["lite_wallet"] = lite;
}
j["verbose_logging"] = verbose_logging_;
j["debug_categories"] = json::array();
for (const auto& cat : debug_categories_)