Files
ObsidianDragon/src/wallet/lite_sync_service.h
DanS fb481dee81 feat(lite): M2b-1/2 — shared-bridge refactor + sync/refresh into WalletState
Shared-bridge refactor (litelib is a global singleton; every LiteClientBridge calls
litelib_shutdown() on destruction, so services must not each own one):
- LiteWalletLifecycleService, LiteWalletGateway, LiteSyncService now take a non-owning
  LiteClientBridge*; LiteWalletController owns the single bridge and passes &bridge_.

Sync + controller refresh:
- LiteSyncService::startSync executes the real "sync" command (was a stub).
- LiteWalletController: startSync() (auto-fires when a wallet becomes ready) and
  refreshWalletState(WalletState&) — polls syncstatus, runs gateway.refresh(), maps the
  bundle, applies balances/addresses/transactions/sync into WalletState.

Tests:
- fake_lite_backend.h returns command-shaped JSON (per tests/fixtures/lite/result_parsers.json).
- testLiteWalletControllerRefreshPopulatesState drives the full path against the fake.
- Surfaced + worked around a real integration issue: parseLiteInfoResponse requires
  latest_block_height and the gateway aborts the whole refresh on the first command's
  parse failure (fragile vs partial backend responses; hardening tracked for M2b-3).

Verified: ctest green; lite+backend, full-node, lite-no-backend apps + lite_smoke build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:24:18 -05:00

162 lines
5.0 KiB
C++

#pragma once
#include "lite_client_bridge.h"
#include "lite_connection_service.h"
#include "lite_result_parsers.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"
#include <cstddef>
#include <cstdint>
#include <string>
namespace dragonx::wallet {
enum class LiteSyncOperation {
StartSync,
PollSyncStatus,
};
enum class LiteSyncStartMode {
Startup,
Restore,
Rescan,
Recovery,
};
enum class LiteSyncAvailability {
Ready,
UnsupportedBuild,
BackendUnavailable,
BridgeUnavailable,
BridgeCallsDisabled,
NoUsableServer,
};
enum class LiteSyncRecoveryDecisionKind {
KeepPolling,
SyncComplete,
Stuck,
ReorgDetected,
InvalidStatus,
};
struct LiteSyncStartRequest {
LiteSyncStartMode mode = LiteSyncStartMode::Startup;
std::string serverUrl;
bool forceRescan = false;
bool afterRestore = false;
};
struct LiteSyncStatusRequest {
std::string serverUrl;
};
struct LiteSyncPlan {
bool ok = false;
LiteSyncOperation operation = LiteSyncOperation::StartSync;
LiteSyncStartMode startMode = LiteSyncStartMode::Startup;
LiteServerEndpoint server;
std::size_t serverIndex = 0;
bool customServer = false;
bool bridgeExecutionAllowed = false;
std::string command;
std::string args;
bool forceRescan = false;
bool afterRestore = false;
std::string error;
};
struct LiteSyncStartResult {
bool ok = false;
bool attempted = false;
bool syncStarted = false;
LiteSyncPlan plan;
WalletBackendStatus status;
std::string error;
};
struct LiteSyncStatusResult {
bool ok = false;
bool attempted = false;
bool bridgeAccepted = false;
LiteSyncPlan plan;
LiteSyncStatusResponse syncStatus;
LiteResultParserError parserError = LiteResultParserError::None;
WalletBackendStatus status;
std::string bridgeResponseRedacted = "<empty>";
std::string error;
};
struct LiteSyncRecoveryInput {
bool havePreviousSyncedBlocks = false;
std::uint64_t previousSyncedBlocks = 0;
std::uint64_t syncedBlocks = 0;
std::uint64_t totalBlocks = 0;
std::uint64_t stagnantPollCount = 0;
std::uint64_t stagnantPollThreshold = 10;
};
struct LiteSyncRecoveryDecision {
LiteSyncRecoveryDecisionKind kind = LiteSyncRecoveryDecisionKind::KeepPolling;
bool shouldPollAgain = true;
bool shouldClear = false;
bool shouldRescan = false;
bool shouldRestartSync = false;
bool requiresUserAttention = false;
std::string reason;
};
struct LiteSyncServiceOptions {
bool allowSyncStatusBridgeCalls = false;
};
const char* liteSyncOperationName(LiteSyncOperation operation);
const char* liteSyncStartModeName(LiteSyncStartMode mode);
const char* liteSyncAvailabilityName(LiteSyncAvailability availability);
const char* liteSyncRecoveryDecisionKindName(LiteSyncRecoveryDecisionKind kind);
WalletBackendStatus walletStatusFromLiteSyncStatus(const LiteSyncStatusResponse& syncStatus);
LiteSyncRecoveryDecision evaluateLiteSyncRecovery(const LiteSyncRecoveryInput& input);
class LiteSyncService {
public:
LiteSyncService(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge* bridge,
LiteSyncServiceOptions options = {});
const WalletCapabilities& capabilities() const { return capabilities_; }
const LiteConnectionSettings& connectionSettings() const { return connectionSettings_; }
const LiteSyncServiceOptions& options() const { return options_; }
LiteSyncAvailability availability() const;
WalletBackendStatus status() const;
LiteSyncPlan planStartSync(const LiteSyncStartRequest& request) const;
LiteSyncPlan planSyncStatus(const LiteSyncStatusRequest& request) const;
LiteSyncStartResult startSync(const LiteSyncStartRequest& request);
LiteSyncStatusResult pollSyncStatus(const LiteSyncStatusRequest& request);
private:
LiteServerSelectionResult selectServerForRequest(const std::string& serverUrl) const;
LiteSyncPlan makePlan(LiteSyncOperation operation,
const std::string& serverUrl,
bool bridgeExecutionAllowed,
LiteSyncStartMode startMode,
bool forceRescan,
bool afterRestore) const;
WalletBackendStatus statusFor(LiteSyncAvailability availability,
const std::string& detail = {}) const;
LiteSyncStartResult blockedStartResult(const LiteSyncPlan& plan,
const WalletBackendStatus& blockedStatus) const;
LiteSyncStatusResult blockedStatusResult(const LiteSyncPlan& plan,
const WalletBackendStatus& blockedStatus) const;
WalletCapabilities capabilities_;
LiteConnectionSettings connectionSettings_;
LiteClientBridge* bridge_ = nullptr; // non-owning; owned by LiteWalletController
LiteSyncServiceOptions options_;
};
} // namespace dragonx::wallet