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:
407
src/wallet/lite_wallet_server_lifecycle_readiness.cpp
Normal file
407
src/wallet/lite_wallet_server_lifecycle_readiness.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "wallet/lite_wallet_server_lifecycle_readiness.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace dragonx::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);
|
||||
}
|
||||
|
||||
void addIssue(LiteWalletServerLifecycleReadinessResult& result,
|
||||
LiteWalletServerLifecycleReadinessIssue issue,
|
||||
std::string message)
|
||||
{
|
||||
result.issues.push_back(LiteWalletServerLifecycleReadinessIssueInfo{issue, std::move(message)});
|
||||
}
|
||||
|
||||
LiteWalletServerLifecycleReadinessResult stoppedResult(
|
||||
LiteWalletServerLifecycleReadinessResult result,
|
||||
LiteWalletServerLifecycleReadinessStatus status,
|
||||
LiteWalletServerLifecycleReadinessIssue issue,
|
||||
std::string message)
|
||||
{
|
||||
result.status = status;
|
||||
addIssue(result, issue, std::move(message));
|
||||
result.error = result.issues.back().message;
|
||||
result.lifecycleStatus = WalletBackendStatus{WalletBackendState::Unavailable, result.error, {}, {}, 0.0};
|
||||
result.syncLifecycleInput.status = result.lifecycleStatus;
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteRedactedPrivateData redactedPrivateField(LitePrivateDataKind kind, const std::string& value)
|
||||
{
|
||||
return LiteRedactedPrivateData{kind, !trimCopy(value).empty(), redactLitePrivateDataValue(value)};
|
||||
}
|
||||
|
||||
bool privateDataIsRedacted(const std::vector<LiteRedactedPrivateData>& privateData)
|
||||
{
|
||||
for (const auto& item : privateData) {
|
||||
if (!item.present && item.redactedValue != "<empty>") return false;
|
||||
if (item.present && item.redactedValue != "<redacted>") return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LiteWalletSelectedServerDisplayReport displayReportForSelection(
|
||||
const LiteServerSelectionResult& selection)
|
||||
{
|
||||
LiteWalletSelectedServerDisplayReport report;
|
||||
if (!selection.ok) {
|
||||
report.status = LiteWalletSelectedServerDisplayStatus::Missing;
|
||||
report.message = selection.error.empty() ? "no usable lite server is selected" : selection.error;
|
||||
return report;
|
||||
}
|
||||
|
||||
report.ok = true;
|
||||
report.status = selection.customServer
|
||||
? LiteWalletSelectedServerDisplayStatus::CustomServer
|
||||
: LiteWalletSelectedServerDisplayStatus::SelectedServer;
|
||||
report.label = selection.server.label;
|
||||
report.url = selection.server.url;
|
||||
report.serverIndex = selection.serverIndex;
|
||||
report.customServer = selection.customServer;
|
||||
report.message = selection.customServer
|
||||
? "custom lite server selected for display"
|
||||
: "configured lite server selected for display";
|
||||
return report;
|
||||
}
|
||||
|
||||
LiteWalletLifecyclePlan lifecyclePlanForRequest(
|
||||
LiteWalletLifecycleOperation operation,
|
||||
const LiteServerSelectionResult& selection)
|
||||
{
|
||||
LiteWalletLifecyclePlan plan;
|
||||
plan.operation = operation;
|
||||
plan.bridgeExecutionAllowed = false;
|
||||
if (!selection.ok) {
|
||||
plan.error = selection.error.empty() ? "no usable lite server is selected" : selection.error;
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.ok = true;
|
||||
plan.server = selection.server;
|
||||
plan.serverIndex = selection.serverIndex;
|
||||
plan.customServer = selection.customServer;
|
||||
return plan;
|
||||
}
|
||||
|
||||
LiteWalletLifecycleUiRequestPlan requestPlanForInput(
|
||||
const LiteWalletServerLifecycleReadinessInput& input,
|
||||
const LiteServerSelectionResult& selection)
|
||||
{
|
||||
LiteWalletLifecycleUiRequestPlan plan;
|
||||
plan.operation = input.operation;
|
||||
plan.lifecyclePlan = lifecyclePlanForRequest(input.operation, selection);
|
||||
if (!plan.lifecyclePlan.ok) {
|
||||
plan.error = plan.lifecyclePlan.error;
|
||||
return plan;
|
||||
}
|
||||
|
||||
switch (input.operation) {
|
||||
case LiteWalletLifecycleOperation::CreateNew:
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::Passphrase,
|
||||
input.createRequest.passphrase));
|
||||
plan.requestSummary = "operation=create;server=<selected>;passphrase=" +
|
||||
std::string(input.createRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
||||
break;
|
||||
case LiteWalletLifecycleOperation::OpenExisting:
|
||||
if (trimCopy(input.openRequest.walletPath).empty()) {
|
||||
plan.error = "lite wallet open requires a wallet path selected by the UI";
|
||||
return plan;
|
||||
}
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::WalletPath,
|
||||
input.openRequest.walletPath));
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::Passphrase,
|
||||
input.openRequest.passphrase));
|
||||
plan.requestSummary = "operation=open;server=<selected>;wallet_path=<redacted>;passphrase=" +
|
||||
std::string(input.openRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
||||
break;
|
||||
case LiteWalletLifecycleOperation::RestoreFromSeed:
|
||||
if (trimCopy(input.restoreRequest.seedPhrase).empty()) {
|
||||
plan.error = "lite wallet restore requires redacted seed material metadata";
|
||||
return plan;
|
||||
}
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::SeedPhrase,
|
||||
input.restoreRequest.seedPhrase));
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::WalletPath,
|
||||
input.restoreRequest.walletPath));
|
||||
plan.privateData.push_back(redactedPrivateField(
|
||||
LitePrivateDataKind::Passphrase,
|
||||
input.restoreRequest.passphrase));
|
||||
plan.requestSummary = "operation=restore;server=<selected>;seed=<redacted>;wallet_path=" +
|
||||
std::string(input.restoreRequest.walletPath.empty() ? "<empty>" : "<redacted>") +
|
||||
";passphrase=" +
|
||||
std::string(input.restoreRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
||||
break;
|
||||
}
|
||||
|
||||
plan.privateInputsRedacted = privateDataIsRedacted(plan.privateData);
|
||||
plan.lifecyclePlan.privateData = plan.privateData;
|
||||
plan.ok = plan.privateInputsRedacted;
|
||||
if (!plan.ok) plan.error = "lite wallet lifecycle private inputs are not redacted";
|
||||
return plan;
|
||||
}
|
||||
|
||||
LiteWalletServerSelectionPersistencePlan persistencePlanForInput(
|
||||
const LiteWalletServerLifecycleReadinessInput& input,
|
||||
const LiteServerSelectionResult& selection)
|
||||
{
|
||||
LiteWalletServerSelectionPersistencePlan plan;
|
||||
plan.settingsLoaded = input.persistence.settingsLoaded;
|
||||
plan.persistedSelectionIntentAccepted = input.persistence.havePersistedSelectionIntent;
|
||||
plan.wouldPersistSelectedServer = input.persistence.persistSelectedServer;
|
||||
plan.persistenceOwnerAccepted = input.persistence.persistenceOwnerReady;
|
||||
plan.selectionMode = input.settings.selectionMode;
|
||||
if (selection.ok) {
|
||||
plan.selectedServerUrl = selection.server.url;
|
||||
plan.selectedServerIndex = selection.serverIndex;
|
||||
plan.selectedServerCustom = selection.customServer;
|
||||
}
|
||||
plan.settingsWritten = false;
|
||||
plan.ok = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
WalletBackendStatus readyLifecycleStatus(const LiteWalletSelectedServerDisplayReport& display)
|
||||
{
|
||||
const std::string serverLabel = display.label.empty() ? display.url : display.label;
|
||||
return WalletBackendStatus{
|
||||
WalletBackendState::Disconnected,
|
||||
"lite lifecycle UI readiness accepted for " + serverLabel + "; wallet lifecycle execution is still disabled",
|
||||
{},
|
||||
{},
|
||||
0.0
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* liteWalletServerLifecycleReadinessStatusName(
|
||||
LiteWalletServerLifecycleReadinessStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: return "ReadyForFutureLifecycle";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: return "WaitingForServerSelection";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: return "WaitingForPersistenceIntent";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: return "WaitingForDisplayStatus";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: return "WaitingForLifecycleUi";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: return "WaitingForPrivateDataRedaction";
|
||||
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: return "WaitingForSyncPlannerFeed";
|
||||
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: return "RuntimeExecutionDisabled";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const char* liteWalletServerLifecycleReadinessIssueName(
|
||||
LiteWalletServerLifecycleReadinessIssue issue)
|
||||
{
|
||||
switch (issue) {
|
||||
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: return "FullNodeBuild";
|
||||
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: return "PersistedSettingsNotLoaded";
|
||||
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: return "PersistedServerSelectionIntentMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: return "ServerSelectionMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: return "ServerPersistenceOwnerMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: return "SelectedServerDisplayMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: return "LifecycleUiOwnerMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: return "LifecycleOperationUnconfirmed";
|
||||
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: return "OpenWalletPathMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: return "RestoreSeedMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: return "PrivateDataRedactionMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: return "SyncPlannerFeedMissing";
|
||||
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: return "RealLifecycleExecutionRequested";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const char* liteWalletSelectedServerDisplayStatusName(
|
||||
LiteWalletSelectedServerDisplayStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case LiteWalletSelectedServerDisplayStatus::Hidden: return "Hidden";
|
||||
case LiteWalletSelectedServerDisplayStatus::SelectedServer: return "SelectedServer";
|
||||
case LiteWalletSelectedServerDisplayStatus::CustomServer: return "CustomServer";
|
||||
case LiteWalletSelectedServerDisplayStatus::Missing: return "Missing";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness(
|
||||
const LiteWalletServerLifecycleReadinessInput& input,
|
||||
LiteWalletServerLifecycleReadinessOptions options)
|
||||
{
|
||||
LiteWalletServerLifecycleReadinessResult result;
|
||||
result.capabilities = input.capabilities;
|
||||
result.persistencePlan = persistencePlanForInput(input, {});
|
||||
|
||||
if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild,
|
||||
LiteWalletServerLifecycleReadinessIssue::FullNodeBuild,
|
||||
"lite server lifecycle readiness requires a lite build");
|
||||
}
|
||||
result.liteBuildAccepted = true;
|
||||
|
||||
if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability,
|
||||
LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing,
|
||||
"lite backend capability is required before lifecycle UI readiness can feed sync planners");
|
||||
}
|
||||
result.backendCapabilityAccepted = true;
|
||||
|
||||
if (options.requirePersistedSettingsLoaded && !input.persistence.settingsLoaded) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
||||
LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded,
|
||||
"lite server settings must be loaded before lifecycle UI readiness is evaluated");
|
||||
}
|
||||
|
||||
if (options.requirePersistedSelectionIntent && !input.persistence.havePersistedSelectionIntent) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
||||
LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing,
|
||||
"lite server selection persistence intent is missing");
|
||||
}
|
||||
|
||||
result.selectedServer = selectLiteServer(input.settings);
|
||||
result.persistencePlan = persistencePlanForInput(input, result.selectedServer);
|
||||
if (!result.selectedServer.ok) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection,
|
||||
LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing,
|
||||
result.selectedServer.error.empty()
|
||||
? "no usable lite server is selected"
|
||||
: result.selectedServer.error);
|
||||
}
|
||||
result.serverSelectionAccepted = true;
|
||||
|
||||
if (input.persistence.persistSelectedServer && !input.persistence.persistenceOwnerReady) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
||||
LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing,
|
||||
"lite server selection persistence owner is not ready");
|
||||
}
|
||||
result.persistenceIntentAccepted = true;
|
||||
result.persistencePlan.ok = true;
|
||||
|
||||
result.selectedServerDisplay = displayReportForSelection(result.selectedServer);
|
||||
if (options.requireSelectedServerDisplay && !input.ui.selectedServerDisplayReady) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus,
|
||||
LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing,
|
||||
"selected lite server display status is not ready");
|
||||
}
|
||||
result.selectedServerDisplayAccepted = true;
|
||||
|
||||
if (options.requireLifecycleUiOwner && !input.ui.lifecycleUiOwnerReady) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
||||
LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing,
|
||||
"lite wallet lifecycle UI owner is not ready");
|
||||
}
|
||||
result.lifecycleUiOwnerAccepted = true;
|
||||
|
||||
if (input.ui.realLifecycleExecutionRequested) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled,
|
||||
LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested,
|
||||
"real lite wallet lifecycle execution is disabled in this scaffold");
|
||||
}
|
||||
|
||||
if (options.requireOperationConfirmation && !input.ui.operationConfirmed) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
||||
LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed,
|
||||
"lite wallet lifecycle operation requires explicit UI confirmation");
|
||||
}
|
||||
|
||||
result.requestPlan = requestPlanForInput(input, result.selectedServer);
|
||||
if (!result.requestPlan.ok) {
|
||||
const auto issue = input.operation == LiteWalletLifecycleOperation::OpenExisting
|
||||
? LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing
|
||||
: LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing;
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
||||
issue,
|
||||
result.requestPlan.error.empty()
|
||||
? "lite wallet lifecycle UI request is incomplete"
|
||||
: result.requestPlan.error);
|
||||
}
|
||||
result.requestAccepted = true;
|
||||
|
||||
if (options.requirePrivateDataRedaction && !input.ui.privateDataRedactionReady) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction,
|
||||
LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing,
|
||||
"lite wallet lifecycle private-data redaction owner is not ready");
|
||||
}
|
||||
result.privateDataRedactionAccepted = true;
|
||||
|
||||
if (options.requireSyncPlannerFeed && !input.ui.syncPlannerFeedReady) {
|
||||
return stoppedResult(
|
||||
std::move(result),
|
||||
LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed,
|
||||
LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing,
|
||||
"lite lifecycle readiness feed for sync planners is not ready");
|
||||
}
|
||||
result.syncPlannerFeedAccepted = true;
|
||||
|
||||
result.ok = true;
|
||||
result.status = LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle;
|
||||
result.futureLifecycleCouldBeEnabled = true;
|
||||
result.lifecycleReportCouldFeedSyncPlanners = true;
|
||||
result.lifecycleStatus = readyLifecycleStatus(result.selectedServerDisplay);
|
||||
result.syncLifecycleInput.ready = true;
|
||||
result.syncLifecycleInput.status = result.lifecycleStatus;
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteWalletSyncAppRefreshLifecycleInput liteWalletSyncLifecycleInputFromServerLifecycleReadiness(
|
||||
const LiteWalletServerLifecycleReadinessResult& result)
|
||||
{
|
||||
return result.syncLifecycleInput;
|
||||
}
|
||||
|
||||
LiteWalletServerLifecycleReadinessPlanner::LiteWalletServerLifecycleReadinessPlanner(
|
||||
LiteWalletServerLifecycleReadinessOptions options)
|
||||
: options_(options)
|
||||
{
|
||||
}
|
||||
|
||||
LiteWalletServerLifecycleReadinessResult LiteWalletServerLifecycleReadinessPlanner::evaluate(
|
||||
const LiteWalletServerLifecycleReadinessInput& input) const
|
||||
{
|
||||
return evaluateLiteWalletServerLifecycleReadiness(input, options_);
|
||||
}
|
||||
|
||||
} // namespace dragonx::wallet
|
||||
Reference in New Issue
Block a user