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>
This commit is contained in:
309
src/wallet/lite_connection_service.cpp
Normal file
309
src/wallet/lite_connection_service.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "lite_connection_service.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx {
|
||||
namespace wallet {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trimCopy(const std::string& value)
|
||||
{
|
||||
auto begin = value.begin();
|
||||
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
||||
|
||||
auto end = value.end();
|
||||
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
||||
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
bool startsWith(const std::string& value, const char* prefix)
|
||||
{
|
||||
const std::string prefixValue(prefix);
|
||||
return value.size() >= prefixValue.size() &&
|
||||
value.compare(0, prefixValue.size(), prefixValue) == 0;
|
||||
}
|
||||
|
||||
LiteServerEndpoint trimmedEndpoint(const LiteServerEndpoint& endpoint)
|
||||
{
|
||||
LiteServerEndpoint normalized = endpoint;
|
||||
normalized.url = trimCopy(normalized.url);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, LiteServerEndpoint>> usableConfiguredServers(
|
||||
const LiteConnectionSettings& settings)
|
||||
{
|
||||
std::vector<std::pair<std::size_t, LiteServerEndpoint>> servers;
|
||||
for (std::size_t index = 0; index < settings.servers.size(); ++index) {
|
||||
auto endpoint = trimmedEndpoint(settings.servers[index]);
|
||||
if (!endpoint.enabled || !isLiteServerUrlUsable(endpoint.url)) continue;
|
||||
servers.push_back({index, std::move(endpoint)});
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
std::string normalizedChainName(const std::string& chainName)
|
||||
{
|
||||
const std::string normalized = trimCopy(chainName);
|
||||
return normalized.empty() ? kDragonXLiteChainName : normalized;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<LiteServerEndpoint> defaultDragonXLiteServers()
|
||||
{
|
||||
return {
|
||||
{"https://lite.dragonx.is", "DragonX Lite", true},
|
||||
{"https://lite1.dragonx.is", "DragonX Lite 1", true},
|
||||
{"https://lite2.dragonx.is", "DragonX Lite 2", true},
|
||||
{"https://lite3.dragonx.is", "DragonX Lite 3", true},
|
||||
{"https://lite4.dragonx.is", "DragonX Lite 4", true},
|
||||
{"https://lite5.dragonx.is", "DragonX Lite 5", true}
|
||||
};
|
||||
}
|
||||
|
||||
LiteConnectionSettings defaultLiteConnectionSettings()
|
||||
{
|
||||
LiteConnectionSettings settings;
|
||||
settings.servers = defaultDragonXLiteServers();
|
||||
settings.selectionMode = LiteServerSelectionMode::Sticky;
|
||||
settings.stickyServerUrl = kDefaultDragonXLiteServer;
|
||||
settings.chainName = kDragonXLiteChainName;
|
||||
settings.randomSelectionSeed = 0;
|
||||
return settings;
|
||||
}
|
||||
|
||||
bool isLiteServerUrlUsable(const std::string& serverUrl)
|
||||
{
|
||||
const std::string normalized = trimCopy(serverUrl);
|
||||
return startsWith(normalized, "https://") || startsWith(normalized, "http://");
|
||||
}
|
||||
|
||||
const char* liteServerSelectionModeName(LiteServerSelectionMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case LiteServerSelectionMode::Sticky:
|
||||
return "Sticky";
|
||||
case LiteServerSelectionMode::Random:
|
||||
return "Random";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const char* liteConnectionAvailabilityName(LiteConnectionAvailability availability)
|
||||
{
|
||||
switch (availability) {
|
||||
case LiteConnectionAvailability::Ready:
|
||||
return "Ready";
|
||||
case LiteConnectionAvailability::UnsupportedBuild:
|
||||
return "UnsupportedBuild";
|
||||
case LiteConnectionAvailability::BackendUnavailable:
|
||||
return "BackendUnavailable";
|
||||
case LiteConnectionAvailability::BridgeUnavailable:
|
||||
return "BridgeUnavailable";
|
||||
case LiteConnectionAvailability::NoUsableServer:
|
||||
return "NoUsableServer";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings)
|
||||
{
|
||||
auto usableServers = usableConfiguredServers(settings);
|
||||
const std::string stickyServerUrl = trimCopy(settings.stickyServerUrl);
|
||||
|
||||
if (settings.selectionMode == LiteServerSelectionMode::Sticky &&
|
||||
isLiteServerUrlUsable(stickyServerUrl)) {
|
||||
auto match = std::find_if(usableServers.begin(), usableServers.end(), [&](const auto& candidate) {
|
||||
return candidate.second.url == stickyServerUrl;
|
||||
});
|
||||
if (match != usableServers.end()) {
|
||||
return LiteServerSelectionResult{true, match->second, match->first, false, {}};
|
||||
}
|
||||
return LiteServerSelectionResult{
|
||||
true,
|
||||
LiteServerEndpoint{stickyServerUrl, "Custom", true},
|
||||
0,
|
||||
true,
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
if (usableServers.empty()) {
|
||||
return LiteServerSelectionResult{false, {}, 0, false, "no usable lite servers are configured"};
|
||||
}
|
||||
|
||||
if (settings.selectionMode == LiteServerSelectionMode::Random) {
|
||||
const std::size_t selectedIndex = settings.randomSelectionSeed % usableServers.size();
|
||||
return LiteServerSelectionResult{
|
||||
true,
|
||||
usableServers[selectedIndex].second,
|
||||
usableServers[selectedIndex].first,
|
||||
false,
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
return LiteServerSelectionResult{true, usableServers.front().second, usableServers.front().first, false, {}};
|
||||
}
|
||||
|
||||
LiteConnectionService::LiteConnectionService(WalletCapabilities capabilities,
|
||||
LiteConnectionSettings settings,
|
||||
LiteClientBridge bridge)
|
||||
: capabilities_(capabilities), settings_(std::move(settings)), bridge_(std::move(bridge))
|
||||
{
|
||||
}
|
||||
|
||||
LiteConnectionAvailability LiteConnectionService::availability() const
|
||||
{
|
||||
if (!isLiteBuild(capabilities_)) return LiteConnectionAvailability::UnsupportedBuild;
|
||||
if (!supportsLiteBackend(capabilities_)) return LiteConnectionAvailability::BackendUnavailable;
|
||||
if (!bridge_.available()) return LiteConnectionAvailability::BridgeUnavailable;
|
||||
if (!selectedServer().ok) return LiteConnectionAvailability::NoUsableServer;
|
||||
return LiteConnectionAvailability::Ready;
|
||||
}
|
||||
|
||||
WalletBackendStatus LiteConnectionService::status() const
|
||||
{
|
||||
const auto currentAvailability = availability();
|
||||
if (currentAvailability == LiteConnectionAvailability::NoUsableServer) {
|
||||
return statusFor(currentAvailability, selectedServer().error);
|
||||
}
|
||||
return statusFor(currentAvailability);
|
||||
}
|
||||
|
||||
LiteServerSelectionResult LiteConnectionService::selectedServer() const
|
||||
{
|
||||
return selectLiteServer(settings_);
|
||||
}
|
||||
|
||||
LiteWalletExistsRequest LiteConnectionService::walletExistsRequest() const
|
||||
{
|
||||
return LiteWalletExistsRequest{normalizedChainName(settings_.chainName)};
|
||||
}
|
||||
|
||||
LiteServerHealthRequest LiteConnectionService::serverHealthRequest() const
|
||||
{
|
||||
auto selection = selectedServer();
|
||||
if (!selection.ok) return {};
|
||||
return LiteServerHealthRequest{selection.server, selection.serverIndex, selection.customServer};
|
||||
}
|
||||
|
||||
LiteWalletExistsCheckResult LiteConnectionService::checkWalletExists()
|
||||
{
|
||||
LiteWalletExistsCheckResult result;
|
||||
result.request = walletExistsRequest();
|
||||
|
||||
const auto readiness = bridgeReadinessStatus();
|
||||
if (readiness.state != WalletBackendState::Disconnected) {
|
||||
result.status = readiness;
|
||||
result.error = readiness.message;
|
||||
return result;
|
||||
}
|
||||
if (result.request.chainName.empty()) {
|
||||
result.status = statusFor(LiteConnectionAvailability::BackendUnavailable, "lite chain name is empty");
|
||||
result.error = result.status.message;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.attempted = true;
|
||||
result.walletExists = bridge_.walletExists(result.request.chainName);
|
||||
result.ok = true;
|
||||
result.status = readiness;
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteServerHealthCheckResult LiteConnectionService::checkSelectedServerHealth()
|
||||
{
|
||||
LiteServerHealthCheckResult result;
|
||||
result.request = serverHealthRequest();
|
||||
|
||||
const auto readiness = bridgeReadinessStatus();
|
||||
if (readiness.state != WalletBackendState::Disconnected) {
|
||||
result.status = readiness;
|
||||
result.error = readiness.message;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto selection = selectedServer();
|
||||
if (!selection.ok) {
|
||||
result.status = statusFor(LiteConnectionAvailability::NoUsableServer, selection.error);
|
||||
result.error = result.status.message;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.request = LiteServerHealthRequest{selection.server, selection.serverIndex, selection.customServer};
|
||||
result.attempted = true;
|
||||
result.serverOnline = bridge_.checkServerOnline(result.request.server.url);
|
||||
result.ok = true;
|
||||
result.status = readiness;
|
||||
return result;
|
||||
}
|
||||
|
||||
WalletBackendStatus LiteConnectionService::statusFor(LiteConnectionAvailability availability,
|
||||
const std::string& detail) const
|
||||
{
|
||||
switch (availability) {
|
||||
case LiteConnectionAvailability::Ready:
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Disconnected,
|
||||
detail.empty() ? "lite connection scaffold ready; wallet lifecycle is not implemented" : detail,
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
case LiteConnectionAvailability::UnsupportedBuild:
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Unavailable,
|
||||
"lite connection service is unsupported in full-node builds",
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
case LiteConnectionAvailability::BackendUnavailable:
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Unavailable,
|
||||
detail.empty() ? "lite backend is not linked" : detail,
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
case LiteConnectionAvailability::BridgeUnavailable:
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Unavailable,
|
||||
detail.empty() ? bridge_.unavailableReason() : detail,
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
case LiteConnectionAvailability::NoUsableServer:
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Error,
|
||||
detail.empty() ? "no usable lite servers are configured" : detail,
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
}
|
||||
|
||||
return WalletBackendStatus{WalletBackendState::Unavailable, "unknown lite connection state", {}, {}, 0.0};
|
||||
}
|
||||
|
||||
WalletBackendStatus LiteConnectionService::bridgeReadinessStatus() const
|
||||
{
|
||||
if (!isLiteBuild(capabilities_)) return statusFor(LiteConnectionAvailability::UnsupportedBuild);
|
||||
if (!supportsLiteBackend(capabilities_)) return statusFor(LiteConnectionAvailability::BackendUnavailable);
|
||||
if (!bridge_.available()) return statusFor(LiteConnectionAvailability::BridgeUnavailable);
|
||||
return statusFor(LiteConnectionAvailability::Ready);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user