Files
ObsidianDragon/src/wallet/lite_wallet_server_selection_adapter.cpp
DanS 863d015628 feat(lite): lite wallet foundation (inherited working-tree state)
Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
  sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
  services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
  lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
  scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
  mining_tab, bootstrap dialog, and version headers captured as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:28 -05:00

463 lines
23 KiB
C++

#include "lite_wallet_server_selection_adapter.h"
#include <algorithm>
#include <cctype>
#include <sstream>
#include <utility>
namespace dragonx {
namespace wallet {
namespace {
std::string trimCopy(const std::string& value)
{
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
return std::isspace(ch) != 0;
});
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
return std::isspace(ch) != 0;
}).base();
if (first >= last) return {};
return std::string(first, last);
}
LiteServerSelectionMode walletModeFromSettings(
config::Settings::LiteServerSelectionPreferenceMode mode)
{
switch (mode) {
case config::Settings::LiteServerSelectionPreferenceMode::Sticky:
return LiteServerSelectionMode::Sticky;
case config::Settings::LiteServerSelectionPreferenceMode::Random:
return LiteServerSelectionMode::Random;
}
return LiteServerSelectionMode::Sticky;
}
config::Settings::LiteServerSelectionPreferenceMode settingsModeFromWallet(
LiteServerSelectionMode mode)
{
switch (mode) {
case LiteServerSelectionMode::Sticky:
return config::Settings::LiteServerSelectionPreferenceMode::Sticky;
case LiteServerSelectionMode::Random:
return config::Settings::LiteServerSelectionPreferenceMode::Random;
}
return config::Settings::LiteServerSelectionPreferenceMode::Sticky;
}
void addIssue(std::vector<LiteWalletServerSelectionUiExecutionIssueInfo>& issues,
LiteWalletServerSelectionUiExecutionIssue issue,
std::string message)
{
issues.push_back(LiteWalletServerSelectionUiExecutionIssueInfo{issue, std::move(message)});
}
LiteWalletServerSelectionUiExecutionStatus statusFromLifecycle(
LiteWalletServerLifecycleReadinessStatus status)
{
switch (status) {
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle:
return LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild;
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings;
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection;
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction;
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed;
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled:
return LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled;
}
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings;
}
LiteWalletServerSelectionUiExecutionIssue issueFromLifecycle(
LiteWalletServerLifecycleReadinessIssue issue)
{
switch (issue) {
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild:
return LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild;
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing:
return LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing;
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded:
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing:
return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded;
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing:
return LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing;
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing:
return LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing;
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing:
return LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing;
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing:
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed:
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing:
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing:
return LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing;
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing:
return LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing;
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing:
return LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing;
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested:
return LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested;
}
return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded;
}
void copyLifecycleIssues(LiteWalletServerSelectionUiExecutionResult& result)
{
for (const auto& issue : result.lifecycleReadiness.issues) {
addIssue(result.issues, issueFromLifecycle(issue.issue), issue.message);
}
}
LiteWalletServerSelectionUiExecutionResult stoppedResult(
LiteWalletServerSelectionUiExecutionResult result,
LiteWalletServerSelectionUiExecutionStatus status,
LiteWalletServerSelectionUiExecutionIssue issue,
const std::string& message)
{
result.status = status;
result.error = message;
result.ok = false;
addIssue(result.issues, issue, message);
return result;
}
std::string buildDiagnosticSummary(const LiteWalletServerSelectionUiExecutionResult& result)
{
std::ostringstream out;
out << "mode=" << liteServerSelectionModeName(result.connectionSettings.selectionMode)
<< ";server=" << result.selectedServerUrlRedacted
<< ";settings_written=" << (result.settingsWritten ? "true" : "false")
<< ";network=false;bridge=false;lifecycle=false;sync=false;wallet_state=false";
return out.str();
}
LiteConnectionSettings settingsWithIntent(const LiteConnectionSettings& current,
const LiteWalletServerSelectionUiIntent& intent)
{
LiteConnectionSettings settings = current;
if (!intent.selectedServerIntentProvided) return settings;
settings.selectionMode = intent.selectionMode;
if (!intent.chainName.empty()) settings.chainName = trimCopy(intent.chainName);
if (intent.replaceServers) settings.servers = intent.servers;
switch (intent.selectionMode) {
case LiteServerSelectionMode::Sticky:
settings.stickyServerUrl = trimCopy(intent.selectedServerUrl);
break;
case LiteServerSelectionMode::Random:
settings.randomSelectionSeed = intent.randomSelectionSeed;
break;
}
return settings;
}
LiteWalletServerLifecycleReadinessInput makeLifecycleInput(
const LiteWalletServerSelectionUiExecutionInput& input,
const LiteConnectionSettings& connectionSettings)
{
LiteWalletServerLifecycleReadinessInput lifecycle;
lifecycle.capabilities = input.capabilities;
lifecycle.settings = connectionSettings;
lifecycle.operation = input.operation;
lifecycle.persistence.settingsLoaded = input.persistence.settingsLoaded;
lifecycle.persistence.havePersistedSelectionIntent =
input.persistence.havePersistedSelectionIntent || input.intent.selectedServerIntentProvided;
lifecycle.persistence.persistSelectedServer = input.persistence.persistSelectedServer;
lifecycle.persistence.persistenceOwnerReady = input.persistence.persistenceOwnerReady;
lifecycle.ui = input.ui;
lifecycle.ui.realLifecycleExecutionRequested =
lifecycle.ui.realLifecycleExecutionRequested || input.walletLifecycleExecutionRequested;
lifecycle.createRequest = input.createRequest;
lifecycle.openRequest = input.openRequest;
lifecycle.restoreRequest = input.restoreRequest;
return lifecycle;
}
} // namespace
LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Settings& settings)
{
LiteConnectionSettings connectionSettings;
connectionSettings.servers.clear();
for (const auto& server : settings.getLiteServers()) {
connectionSettings.servers.push_back(LiteServerEndpoint{
trimCopy(server.url),
server.label,
server.enabled
});
}
connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode());
connectionSettings.stickyServerUrl = trimCopy(settings.getLiteStickyServerUrl());
connectionSettings.chainName = trimCopy(settings.getLiteChainName());
if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName;
connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed();
return connectionSettings;
}
void applyLiteConnectionSettingsToAppSettings(config::Settings& settings,
const LiteConnectionSettings& connectionSettings)
{
std::vector<config::Settings::LiteServerPreference> servers;
servers.reserve(connectionSettings.servers.size());
for (const auto& server : connectionSettings.servers) {
servers.push_back(config::Settings::LiteServerPreference{
trimCopy(server.url),
server.label,
server.enabled
});
}
settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode));
settings.setLiteStickyServerUrl(trimCopy(connectionSettings.stickyServerUrl));
settings.setLiteChainName(trimCopy(connectionSettings.chainName).empty()
? kDragonXLiteChainName
: trimCopy(connectionSettings.chainName));
settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed);
settings.setLiteServers(servers);
}
const char* liteWalletServerSelectionUiExecutionStatusName(
LiteWalletServerSelectionUiExecutionStatus status)
{
switch (status) {
case LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle:
return "ReadyForFutureLifecycle";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild:
return "WaitingForLiteBuild";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability:
return "WaitingForBackendCapability";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings:
return "WaitingForSettings";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection:
return "WaitingForServerSelection";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner:
return "WaitingForPersistenceOwner";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus:
return "WaitingForDisplayStatus";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi:
return "WaitingForLifecycleUi";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction:
return "WaitingForPrivateDataRedaction";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed:
return "WaitingForSyncPlannerFeed";
case LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed:
return "SettingsSaveFailed";
case LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled:
return "RuntimeExecutionDisabled";
}
return "WaitingForSettings";
}
const char* liteWalletServerSelectionUiExecutionIssueName(
LiteWalletServerSelectionUiExecutionIssue issue)
{
switch (issue) {
case LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild:
return "FullNodeBuild";
case LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing:
return "LiteBackendCapabilityMissing";
case LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded:
return "SettingsNotLoaded";
case LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing:
return "ServerSelectionMissing";
case LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing:
return "ServerPersistenceOwnerMissing";
case LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing:
return "SelectedServerDisplayMissing";
case LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing:
return "LifecycleUiOwnerMissing";
case LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing:
return "PrivateDataRedactionMissing";
case LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing:
return "SyncPlannerFeedMissing";
case LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed:
return "SettingsSaveFailed";
case LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested:
return "ServerConnectivityCheckRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested:
return "WalletLifecycleExecutionRequested";
case LiteWalletServerSelectionUiExecutionIssue::SyncRequested:
return "SyncRequested";
case LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested:
return "SyncStatusPollingRequested";
case LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested:
return "WorkerQueueRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested:
return "WalletStateMutationRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested:
return "WalletFilePersistenceRequested";
case LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested:
return "SendImportExportRequested";
}
return "SettingsNotLoaded";
}
std::string redactLiteServerSelectionValue(const std::string& value)
{
const std::string trimmed = trimCopy(value);
if (trimmed.empty()) return "<empty>";
const auto scheme = trimmed.find("://");
if (scheme == std::string::npos) return "<redacted>";
return trimmed.substr(0, scheme + 3) + "<redacted>";
}
LiteWalletServerSelectionUiExecutionResult executeLiteWalletServerSelectionUi(
config::Settings& settings,
const LiteWalletServerSelectionUiExecutionInput& input)
{
LiteWalletServerSelectionUiExecutionResult result;
auto stopForRuntime = [&](LiteWalletServerSelectionUiExecutionIssue issue,
const std::string& message) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled,
issue,
message);
};
if (input.serverConnectivityCheckRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested,
"lite server selection settings adapter cannot check server connectivity");
}
if (input.walletExistsCheckRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested,
"lite server selection settings adapter cannot check wallet existence");
}
if (input.walletLifecycleExecutionRequested || input.ui.realLifecycleExecutionRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested,
"lite server selection settings adapter cannot execute wallet lifecycle actions");
}
if (input.syncStartRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncRequested,
"lite server selection settings adapter cannot start sync");
}
if (input.syncStatusPollingRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested,
"lite server selection settings adapter cannot poll syncstatus");
}
if (input.workerQueueEnqueueRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested,
"lite server selection settings adapter cannot enqueue wallet workers");
}
if (input.walletStateMutationRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested,
"lite server selection settings adapter cannot mutate WalletState");
}
if (input.walletFilePersistenceRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested,
"lite server selection settings adapter cannot persist wallet files");
}
if (input.sendImportExportExecutionRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested,
"lite server selection settings adapter cannot execute send/import/export flows");
}
if (!input.persistence.settingsLoaded) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings,
LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded,
"lite server settings are not loaded");
}
result.connectionSettings = settingsWithIntent(
liteConnectionSettingsFromAppSettings(settings), input.intent);
if (input.intent.selectedServerIntentProvided &&
input.intent.selectionMode == LiteServerSelectionMode::Sticky &&
!isLiteServerUrlUsable(trimCopy(input.intent.selectedServerUrl))) {
result.selectedServerUrlRedacted = redactLiteServerSelectionValue(input.intent.selectedServerUrl);
result.selectedServerRedacted = true;
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection,
LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing,
"lite server selection URL is not usable");
}
result.selectedServer = selectLiteServer(result.connectionSettings);
result.selectedServerUrlRedacted = result.selectedServer.ok
? redactLiteServerSelectionValue(result.selectedServer.server.url)
: redactLiteServerSelectionValue(result.connectionSettings.stickyServerUrl);
result.selectedServerRedacted = true;
if (!result.selectedServer.ok) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection,
LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing,
result.selectedServer.error.empty()
? "lite server selection is missing"
: result.selectedServer.error);
}
result.selectedServerIntentAccepted = true;
if ((input.persistence.persistSelectedServer || input.persistence.writeSettings) &&
!input.persistence.persistenceOwnerReady) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner,
LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing,
"lite server settings persistence owner is not ready");
}
result.settingsPersistenceAccepted = true;
if (input.persistence.persistSelectedServer || input.persistence.writeSettings) {
applyLiteConnectionSettingsToAppSettings(settings, result.connectionSettings);
settings.setLitePersistSelectedServer(input.persistence.persistSelectedServer);
result.settingsMutated = true;
}
if (input.persistence.writeSettings) {
result.noSettingsPersistence = false;
const bool saved = input.persistence.settingsSavePath.empty()
? settings.save()
: settings.save(input.persistence.settingsSavePath);
if (!saved) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed,
LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed,
"failed to save lite server settings");
}
result.settingsWritten = true;
}
result.lifecycleInput = makeLifecycleInput(input, result.connectionSettings);
result.lifecycleReadiness = evaluateLiteWalletServerLifecycleReadiness(result.lifecycleInput);
result.lifecycleReadinessAccepted = result.lifecycleReadiness.ok;
result.lifecycleReportCouldFeedSyncPlanners = result.lifecycleReadiness.lifecycleReportCouldFeedSyncPlanners;
result.status = statusFromLifecycle(result.lifecycleReadiness.status);
copyLifecycleIssues(result);
result.diagnosticSummary = buildDiagnosticSummary(result);
if (input.requireLifecycleReadiness && !result.lifecycleReadiness.ok) {
result.ok = false;
result.error = result.lifecycleReadiness.error;
return result;
}
result.ok = true;
return result;
}
LiteWalletServerSelectionUiExecutionAdapter::LiteWalletServerSelectionUiExecutionAdapter(
config::Settings& settings)
: settings_(settings)
{
}
LiteWalletServerSelectionUiExecutionResult LiteWalletServerSelectionUiExecutionAdapter::execute(
const LiteWalletServerSelectionUiExecutionInput& input) const
{
return executeLiteWalletServerSelectionUi(settings_, input);
}
} // namespace wallet
} // namespace dragonx