#include "wallet/lite_wallet_refresh_readiness_policy.h" #include namespace dragonx::wallet { namespace { void addIssue(LiteWalletRefreshReadinessResult& result, LiteWalletRefreshReadinessIssue issue, std::string message) { result.issues.push_back(LiteWalletRefreshReadinessIssueInfo{issue, std::move(message)}); } std::string statusMessageOrDefault(const WalletBackendStatus& status, const std::string& fallback) { return status.message.empty() ? fallback : status.message; } LiteWalletRefreshReadinessResult failReadiness( LiteWalletRefreshReadinessResult result, LiteWalletRefreshReadinessStatus status, LiteWalletRefreshReadinessIssue issue, std::string message) { result.status = status; addIssue(result, issue, std::move(message)); result.error = result.issues.back().message; return result; } bool coordinatorReportIsUnsafe(const LiteWalletAppRefreshCoordinationResult& coordination) { return !coordination.dryRunOnly || !coordination.noNetwork || coordination.stateMutationRequested || coordination.stateMutationAllowed || coordination.stateMutated || coordination.walletStateWritten; } } // namespace const char* liteWalletRefreshReadinessStatusName(LiteWalletRefreshReadinessStatus status) { switch (status) { case LiteWalletRefreshReadinessStatus::EligibleDryRun: return "EligibleDryRun"; case LiteWalletRefreshReadinessStatus::CoordinatorRejected: return "CoordinatorRejected"; case LiteWalletRefreshReadinessStatus::UnsafeReport: return "UnsafeReport"; case LiteWalletRefreshReadinessStatus::WaitingForLifecycle: return "WaitingForLifecycle"; case LiteWalletRefreshReadinessStatus::WaitingForSync: return "WaitingForSync"; case LiteWalletRefreshReadinessStatus::Stale: return "Stale"; } return "Unknown"; } const char* liteWalletRefreshReadinessIssueName(LiteWalletRefreshReadinessIssue issue) { switch (issue) { case LiteWalletRefreshReadinessIssue::CoordinatorReportRejected: return "CoordinatorReportRejected"; case LiteWalletRefreshReadinessIssue::CoordinatorReportIncomplete: return "CoordinatorReportIncomplete"; case LiteWalletRefreshReadinessIssue::CoordinatorReportUnsafe: return "CoordinatorReportUnsafe"; case LiteWalletRefreshReadinessIssue::FullNodeRouteRejected: return "FullNodeRouteRejected"; case LiteWalletRefreshReadinessIssue::LifecycleNotReady: return "LifecycleNotReady"; case LiteWalletRefreshReadinessIssue::SyncNotReady: return "SyncNotReady"; case LiteWalletRefreshReadinessIssue::RefreshTimestampMissing: return "RefreshTimestampMissing"; case LiteWalletRefreshReadinessIssue::RefreshTimestampInFuture: return "RefreshTimestampInFuture"; case LiteWalletRefreshReadinessIssue::RefreshStale: return "RefreshStale"; case LiteWalletRefreshReadinessIssue::StaleRefreshAllowed: return "StaleRefreshAllowed"; } return "Unknown"; } LiteWalletRefreshReadinessResult evaluateLiteWalletRefreshReadiness( const LiteWalletAppRefreshCoordinationResult& coordination, const LiteWalletRefreshReadinessInputs& inputs, LiteWalletRefreshReadinessPolicyOptions options) { LiteWalletRefreshReadinessResult result; result.route = coordination.route; result.dryRunOnly = coordination.dryRunOnly; result.noNetwork = coordination.noNetwork; result.stateMutationRequested = coordination.stateMutationRequested; result.stateMutationAllowed = coordination.stateMutationAllowed; result.stateMutated = coordination.stateMutated; result.walletStateWritten = coordination.walletStateWritten; result.lifecycleReady = inputs.lifecycleReady; result.lifecycleStatus = inputs.lifecycleStatus; result.syncReady = inputs.syncReady; result.syncStatus = inputs.syncStatus; result.maxRefreshAgeSeconds = options.maxRefreshAgeSeconds; if (coordination.fullNodeRefreshDelegated || coordination.route == LiteWalletRefreshRouteKind::FullNodeRpc) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::CoordinatorRejected, LiteWalletRefreshReadinessIssue::FullNodeRouteRejected, "full-node refresh reports are not eligible for lite UI refresh readiness"); } if (!coordination.ok) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::CoordinatorRejected, LiteWalletRefreshReadinessIssue::CoordinatorReportRejected, coordination.error.empty() ? "lite app refresh coordinator report is not successful" : coordination.error); } result.coordinatorReportAccepted = true; if (!coordination.mapped || !coordination.planned || !coordination.executionReported || coordination.successfulCommandCount == 0) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::CoordinatorRejected, LiteWalletRefreshReadinessIssue::CoordinatorReportIncomplete, "lite app refresh coordinator report is incomplete"); } if (coordinatorReportIsUnsafe(coordination)) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::UnsafeReport, LiteWalletRefreshReadinessIssue::CoordinatorReportUnsafe, "lite app refresh coordinator report is not a no-network dry-run report"); } if (options.requireLifecycleReady && !inputs.lifecycleReady) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::WaitingForLifecycle, LiteWalletRefreshReadinessIssue::LifecycleNotReady, statusMessageOrDefault(inputs.lifecycleStatus, "lite wallet lifecycle is not ready")); } if (options.requireSyncReady && !inputs.syncReady) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::WaitingForSync, LiteWalletRefreshReadinessIssue::SyncNotReady, statusMessageOrDefault(inputs.syncStatus, "lite wallet sync is not ready")); } if (options.requireFreshRefresh) { if (!inputs.freshness.haveSnapshotTime) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::Stale, LiteWalletRefreshReadinessIssue::RefreshTimestampMissing, "lite refresh snapshot time is not known"); } if (inputs.freshness.snapshotTimeSeconds > inputs.freshness.nowSeconds) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::Stale, LiteWalletRefreshReadinessIssue::RefreshTimestampInFuture, "lite refresh snapshot time is in the future"); } result.refreshAgeSeconds = inputs.freshness.nowSeconds - inputs.freshness.snapshotTimeSeconds; if (result.refreshAgeSeconds > options.maxRefreshAgeSeconds) { result.refreshStale = true; if (!options.allowStaleRefresh) { return failReadiness( std::move(result), LiteWalletRefreshReadinessStatus::Stale, LiteWalletRefreshReadinessIssue::RefreshStale, "lite refresh coordinator report is stale"); } result.staleRefreshAllowed = true; addIssue(result, LiteWalletRefreshReadinessIssue::StaleRefreshAllowed, "stale lite refresh coordinator report is allowed for reporting only"); } else { result.refreshFresh = true; } } else { result.refreshFresh = true; } result.ok = true; result.eligibleForFutureUiRefresh = true; result.status = LiteWalletRefreshReadinessStatus::EligibleDryRun; return result; } LiteWalletRefreshReadinessPolicy::LiteWalletRefreshReadinessPolicy( LiteWalletRefreshReadinessPolicyOptions options) : options_(options) { } LiteWalletRefreshReadinessResult LiteWalletRefreshReadinessPolicy::evaluate( const LiteWalletAppRefreshCoordinationResult& coordination, const LiteWalletRefreshReadinessInputs& inputs) const { return evaluateLiteWalletRefreshReadiness(coordination, inputs, options_); } } // namespace dragonx::wallet