#include "wallet/lite_wallet_sync_app_refresh_integration.h" #include 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