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:
205
src/wallet/lite_wallet_refresh_readiness_policy.cpp
Normal file
205
src/wallet/lite_wallet_refresh_readiness_policy.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user