#include "lite_wallet_server_selection_adapter.h" #include #include #include #include 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& 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 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 ""; const auto scheme = trimmed.find("://"); if (scheme == std::string::npos) return ""; return trimmed.substr(0, scheme + 3) + ""; } 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