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>
344 lines
16 KiB
C++
344 lines
16 KiB
C++
#include "wallet/lite_wallet_sync_app_refresh_integration.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace dragonx::wallet {
|
|
namespace {
|
|
|
|
void addIssue(LiteWalletSyncAppRefreshIntegrationResult& result,
|
|
LiteWalletSyncAppRefreshIntegrationIssue issue,
|
|
std::string message)
|
|
{
|
|
result.issues.push_back(LiteWalletSyncAppRefreshIntegrationIssueInfo{issue, std::move(message)});
|
|
}
|
|
|
|
std::string statusMessageOrDefault(const WalletBackendStatus& status,
|
|
const std::string& fallback)
|
|
{
|
|
return status.message.empty() ? fallback : status.message;
|
|
}
|
|
|
|
bool refreshReportIsUnsafe(const LiteWalletAppRefreshOrchestrationResult& refresh)
|
|
{
|
|
return !refresh.dryRunOnly ||
|
|
!refresh.noNetwork ||
|
|
refresh.stateMutationRequested ||
|
|
refresh.stateMutationAllowed ||
|
|
refresh.stateMutated ||
|
|
refresh.walletStateWritten;
|
|
}
|
|
|
|
bool syncStartPlanIsValid(const LiteSyncPlan& plan)
|
|
{
|
|
return plan.ok && plan.operation == LiteSyncOperation::StartSync && plan.command == "sync";
|
|
}
|
|
|
|
bool syncStatusPlanIsValid(const LiteSyncPlan& plan)
|
|
{
|
|
return plan.ok && plan.operation == LiteSyncOperation::PollSyncStatus && plan.command == "syncstatus";
|
|
}
|
|
|
|
LiteWalletSyncAppRefreshIntegrationResult stoppedResult(
|
|
LiteWalletSyncAppRefreshIntegrationResult result,
|
|
LiteWalletSyncAppRefreshIntegrationStatus status,
|
|
LiteWalletSyncAppRefreshIntegrationIssue issue,
|
|
std::string message)
|
|
{
|
|
result.status = status;
|
|
addIssue(result, issue, std::move(message));
|
|
result.error = result.issues.back().message;
|
|
return result;
|
|
}
|
|
|
|
LiteWalletSyncAppRefreshIntegrationResult skippedResult(
|
|
LiteWalletSyncAppRefreshIntegrationResult result,
|
|
LiteWalletSyncAppRefreshIntegrationIssue issue,
|
|
std::string message)
|
|
{
|
|
result.ok = true;
|
|
result.status = LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued;
|
|
addIssue(result, issue, std::move(message));
|
|
return result;
|
|
}
|
|
|
|
void copyRecoveryFlags(LiteWalletSyncAppRefreshIntegrationResult& result,
|
|
const LiteSyncRecoveryDecision& decision)
|
|
{
|
|
result.recoveryDecision = decision;
|
|
result.futureClearRequired = decision.shouldClear;
|
|
result.futureRescanRequired = decision.shouldRescan;
|
|
result.futureRestartSyncRequired = decision.shouldRestartSync;
|
|
result.requiresUserAttention = decision.requiresUserAttention;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char* liteWalletSyncAppRefreshIntegrationStatusName(
|
|
LiteWalletSyncAppRefreshIntegrationStatus status)
|
|
{
|
|
switch (status) {
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::FutureWorkerQueueFeedReady: return "FutureWorkerQueueFeedReady";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForLifecycle: return "WaitingForLifecycle";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan: return "WaitingForSyncPlan";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus: return "WaitingForSyncStatus";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery: return "WaitingForRecovery";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation: return "WaitingForCancellation";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued: return "RefreshNotQueued";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable: return "WorkerQueueUnavailable";
|
|
case LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan: return "UnsafePlan";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
const char* liteWalletSyncAppRefreshIntegrationIssueName(
|
|
LiteWalletSyncAppRefreshIntegrationIssue issue)
|
|
{
|
|
switch (issue) {
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::LifecycleNotReady: return "LifecycleNotReady";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStartPlan: return "MissingSyncStartPlan";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStartPlan: return "InvalidSyncStartPlan";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStartWouldExecute: return "SyncStartWouldExecute";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStatusPlan: return "MissingSyncStatusPlan";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStatusPlan: return "InvalidSyncStatusPlan";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStatusWouldExecute: return "SyncStatusWouldExecute";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::MissingRecoveryDecision: return "MissingRecoveryDecision";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStillPolling: return "SyncStillPolling";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryRequired: return "SyncRecoveryRequired";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryNeedsUserAttention: return "SyncRecoveryNeedsUserAttention";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::CancellationRequired: return "CancellationRequired";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::CancellationUnsupported: return "CancellationUnsupported";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportRejected: return "RefreshReportRejected";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportUnsafe: return "RefreshReportUnsafe";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshSkipped: return "RefreshSkipped";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshBlocked: return "RefreshBlocked";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueueDisabled: return "FutureWorkerQueueDisabled";
|
|
case LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueuePressure: return "FutureWorkerQueuePressure";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
LiteWalletSyncAppRefreshIntegrationResult planLiteWalletSyncAppRefreshIntegration(
|
|
const LiteWalletSyncAppRefreshIntegrationInput& input,
|
|
LiteWalletSyncAppRefreshIntegrationOptions options)
|
|
{
|
|
LiteWalletSyncAppRefreshIntegrationResult result;
|
|
result.lifecycleStatus = input.lifecycle.status;
|
|
result.refreshOrchestration = input.refreshOrchestration;
|
|
result.route = input.refreshOrchestration.route;
|
|
result.refreshWouldQueue = input.refreshOrchestration.wouldQueueRefresh;
|
|
result.refreshSkipped = input.refreshOrchestration.skipped;
|
|
result.refreshBlocked = input.refreshOrchestration.blocked;
|
|
result.futureWorkerQueuePlan.laneName = input.futureWorkerQueue.laneName;
|
|
result.futureWorkerQueuePlan.trigger = input.refreshOrchestration.trigger;
|
|
result.futureWorkerQueuePlan.route = input.refreshOrchestration.route;
|
|
result.futureWorkerQueuePlan.queueDepth = input.futureWorkerQueue.queueDepth;
|
|
result.futureWorkerQueuePlan.maxQueueDepth = input.futureWorkerQueue.maxQueueDepth;
|
|
|
|
if (input.sync.haveStartPlan) {
|
|
result.syncStartPlan = input.sync.startPlan;
|
|
result.futureWorkerQueuePlan.startSyncCommand = input.sync.startPlan.command;
|
|
}
|
|
if (input.sync.haveStatusPlan) {
|
|
result.syncStatusPlan = input.sync.statusPlan;
|
|
result.futureWorkerQueuePlan.syncStatusCommand = input.sync.statusPlan.command;
|
|
}
|
|
if (input.sync.haveRecoveryDecision) {
|
|
copyRecoveryFlags(result, input.sync.recoveryDecision);
|
|
}
|
|
|
|
if (options.requireLifecycleReady && !input.lifecycle.ready) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForLifecycle,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::LifecycleNotReady,
|
|
statusMessageOrDefault(input.lifecycle.status, "lite wallet lifecycle is not ready"));
|
|
}
|
|
result.lifecycleAccepted = true;
|
|
|
|
if (options.requireSyncStartPlan && !input.sync.haveStartPlan) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStartPlan,
|
|
"lite sync start plan is required before app refresh integration");
|
|
}
|
|
|
|
if (input.sync.haveStartPlan) {
|
|
if (!syncStartPlanIsValid(input.sync.startPlan)) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStartPlan,
|
|
input.sync.startPlan.error.empty()
|
|
? "lite sync start plan is invalid"
|
|
: input.sync.startPlan.error);
|
|
}
|
|
if (options.rejectExecutableSyncPlans && input.sync.startPlan.bridgeExecutionAllowed) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::SyncStartWouldExecute,
|
|
"lite sync start plan would allow bridge execution");
|
|
}
|
|
result.syncStartPlanAccepted = true;
|
|
}
|
|
|
|
if (options.requireSyncStatusPlan && !input.sync.haveStatusPlan) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStatusPlan,
|
|
"lite syncstatus plan is required before app refresh integration");
|
|
}
|
|
|
|
if (input.sync.haveStatusPlan) {
|
|
if (!syncStatusPlanIsValid(input.sync.statusPlan)) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStatusPlan,
|
|
input.sync.statusPlan.error.empty()
|
|
? "lite syncstatus plan is invalid"
|
|
: input.sync.statusPlan.error);
|
|
}
|
|
if (options.rejectExecutableSyncPlans && input.sync.statusPlan.bridgeExecutionAllowed) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::SyncStatusWouldExecute,
|
|
"lite syncstatus plan would allow bridge execution");
|
|
}
|
|
result.syncStatusPlanAccepted = true;
|
|
}
|
|
|
|
if (options.requireRecoveryDecision && !input.sync.haveRecoveryDecision) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::MissingRecoveryDecision,
|
|
"lite sync recovery decision is required before app refresh integration");
|
|
}
|
|
|
|
if (input.sync.haveRecoveryDecision) {
|
|
const auto& decision = input.sync.recoveryDecision;
|
|
if (decision.kind == LiteSyncRecoveryDecisionKind::InvalidStatus) {
|
|
result.recoveryRequired = true;
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryNeedsUserAttention,
|
|
decision.reason.empty()
|
|
? "lite sync status is invalid and needs user attention"
|
|
: decision.reason);
|
|
}
|
|
if (decision.kind == LiteSyncRecoveryDecisionKind::Stuck ||
|
|
decision.kind == LiteSyncRecoveryDecisionKind::ReorgDetected) {
|
|
result.recoveryRequired = true;
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryRequired,
|
|
decision.reason.empty()
|
|
? "lite sync recovery must be modeled before refresh can feed a queue"
|
|
: decision.reason);
|
|
}
|
|
if (options.requireSyncCompleteForRefresh && decision.kind == LiteSyncRecoveryDecisionKind::KeepPolling) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::SyncStillPolling,
|
|
decision.reason.empty()
|
|
? "lite sync is still polling"
|
|
: decision.reason);
|
|
}
|
|
result.recoveryAccepted = true;
|
|
}
|
|
|
|
if (input.cancellation.cancellationRequested && input.cancellation.syncInProgress) {
|
|
result.cancellationRequired = true;
|
|
if (!input.cancellation.cancellationSupported) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::CancellationUnsupported,
|
|
"lite sync cancellation is requested but no cancellation path is available");
|
|
}
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::CancellationRequired,
|
|
"lite sync cancellation must complete before app refresh can feed a queue");
|
|
}
|
|
result.cancellationAccepted = true;
|
|
|
|
if (refreshReportIsUnsafe(input.refreshOrchestration)) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportUnsafe,
|
|
"lite app refresh orchestration report is not a no-network dry-run report");
|
|
}
|
|
|
|
if (!input.refreshOrchestration.ok) {
|
|
const auto issue = input.refreshOrchestration.blocked
|
|
? LiteWalletSyncAppRefreshIntegrationIssue::RefreshBlocked
|
|
: LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportRejected;
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued,
|
|
issue,
|
|
input.refreshOrchestration.error.empty()
|
|
? "lite app refresh orchestration report is not queued"
|
|
: input.refreshOrchestration.error);
|
|
}
|
|
|
|
result.refreshReportAccepted = true;
|
|
|
|
if (input.refreshOrchestration.skipped || !input.refreshOrchestration.wouldQueueRefresh) {
|
|
return skippedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationIssue::RefreshSkipped,
|
|
input.refreshOrchestration.issues.empty()
|
|
? "lite app refresh orchestration skipped queueing"
|
|
: input.refreshOrchestration.issues.front().message);
|
|
}
|
|
|
|
if (options.requireFutureWorkerQueue && !input.futureWorkerQueue.enabled) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueueDisabled,
|
|
"future lite app refresh worker queue is disabled");
|
|
}
|
|
|
|
if (input.futureWorkerQueue.maxQueueDepth > 0 &&
|
|
input.futureWorkerQueue.queueDepth >= input.futureWorkerQueue.maxQueueDepth) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable,
|
|
LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueuePressure,
|
|
"future lite app refresh worker queue is at capacity");
|
|
}
|
|
|
|
result.ok = true;
|
|
result.status = LiteWalletSyncAppRefreshIntegrationStatus::FutureWorkerQueueFeedReady;
|
|
result.futureWorkerQueueFeedReady = true;
|
|
result.wouldFeedFutureWorkerQueue = true;
|
|
result.futureWorkerQueuePlan.readyToFeed = true;
|
|
result.futureWorkerQueuePlan.wouldFeed = true;
|
|
return result;
|
|
}
|
|
|
|
LiteWalletSyncAppRefreshIntegrationPlanner::LiteWalletSyncAppRefreshIntegrationPlanner(
|
|
LiteWalletSyncAppRefreshIntegrationOptions options)
|
|
: options_(options)
|
|
{
|
|
}
|
|
|
|
LiteWalletSyncAppRefreshIntegrationResult LiteWalletSyncAppRefreshIntegrationPlanner::plan(
|
|
const LiteWalletSyncAppRefreshIntegrationInput& input) const
|
|
{
|
|
return planLiteWalletSyncAppRefreshIntegration(input, options_);
|
|
}
|
|
|
|
} // namespace dragonx::wallet
|