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>
205 lines
8.4 KiB
C++
205 lines
8.4 KiB
C++
#include "wallet/lite_wallet_refresh_readiness_policy.h"
|
|
|
|
#include <utility>
|
|
|
|
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
|