Files
ObsidianDragon/src/wallet/lite_result_parsers.h
T
DanSandClaude Opus 4.8 6f369376ec fix(lite): surface sync failures instead of a frozen "Syncing 99.9%"; don't report a lost server as "Synced"
Lite-parity audit, sync-progress semantics (state/presentation only — no send/sync backend call changed):
- A scan failure set scan->failed + lite_error, but no UI read lite_error, so the user saw "Syncing 99.9%"
  forever. The status bar now shows the error text on a lite scan/server failure.
- After the initial catch-up, a server loss on the cheap probe path (ProbeFailed) left the last "Synced"
  model on screen. The worker now re-publishes that model demoted (not complete, reason "server unreachable").
- refreshModelImpl's height-read "synced" override no longer fires while syncstatus says the backend is
  still scanning or is indeterminate, and a server-bound read failure in a full cycle demotes to not-synced.
- The syncstatus parser no longer treats a missing/unrecognised "syncing" field as fully synced: it reports
  an explicit "indeterminate" (not complete). Explicit "false" stays caught-up; the fake backend's
  syncing:true 1000/1000 behaviour is unchanged.

Tests: parser cases for missing/odd/idle "syncing" (testLiteSyncStatus*).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 15:16:36 -05:00

182 lines
5.9 KiB
C++

#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace dragonx::wallet {
enum class LiteResultCommand {
Info,
Height,
Balance,
Addresses,
List,
Notes,
SyncStatus,
};
enum class LiteResultParserError {
None,
InvalidJson,
ExpectedObject,
ExpectedArray,
MissingField,
InvalidFieldType,
InvalidFieldValue,
};
enum class LiteSpendableOutputKind {
UnspentNote,
Utxo,
PendingNote,
PendingUtxo,
};
enum class LiteTransactionDirection {
Unknown,
Send,
Receive,
};
struct LiteResultParserIssue {
std::string path;
std::string message;
};
struct LiteParseResultBase {
bool ok = false;
LiteResultCommand command = LiteResultCommand::Info;
LiteResultParserError error = LiteResultParserError::None;
std::string errorPath;
std::string errorMessage;
std::vector<LiteResultParserIssue> issues;
};
struct LiteInfoResponse {
std::optional<std::string> chainName;
std::optional<std::string> version;
std::optional<std::string> vendor;
std::optional<std::int64_t> latestBlockHeight;
std::optional<std::int64_t> difficulty;
std::optional<std::int64_t> longestChain;
std::optional<std::int64_t> notarized;
};
struct LiteHeightResponse {
std::int64_t height = 0;
};
struct LiteBalanceResponse {
std::uint64_t transparentBalance = 0;
std::uint64_t shieldedBalance = 0;
std::uint64_t unconfirmedBalance = 0;
std::uint64_t verifiedShieldedBalance = 0;
std::uint64_t spendableShieldedBalance = 0;
};
struct LiteAddressesResponse {
std::vector<std::string> zAddresses;
std::vector<std::string> tAddresses;
};
struct LiteSpendableOutput {
LiteSpendableOutputKind kind = LiteSpendableOutputKind::UnspentNote;
std::string address;
std::string createdInTxid;
std::optional<std::int64_t> createdInBlock;
std::uint64_t value = 0;
bool spent = false;
bool unconfirmedSpent = false;
bool pending = false;
bool spendable = false;
};
struct LiteNotesResponse {
std::vector<LiteSpendableOutput> unspentNotes;
std::vector<LiteSpendableOutput> utxos;
std::vector<LiteSpendableOutput> pendingNotes;
std::vector<LiteSpendableOutput> pendingUtxos;
};
struct LiteTransactionOutput {
std::string address;
std::int64_t value = 0;
std::string memo;
};
struct LiteTransactionRecord {
std::string txid;
std::int64_t datetime = 0;
std::optional<std::int64_t> blockHeight;
bool unconfirmed = false;
LiteTransactionDirection direction = LiteTransactionDirection::Unknown;
std::string address;
std::int64_t amount = 0;
std::string memo;
std::optional<std::int64_t> position;
std::vector<LiteTransactionOutput> outgoingMetadata;
};
struct LiteTransactionsResponse {
std::vector<LiteTransactionRecord> transactions;
};
struct LiteSyncStatusResponse {
std::uint64_t syncedBlocks = 0;
std::uint64_t totalBlocks = 0;
double progress = 0.0;
bool complete = false;
bool indeterminate = false; // "syncing" missing/unrecognised: state unknown, NOT assumed synced
};
struct LiteInfoParseResult : LiteParseResultBase { LiteInfoResponse info; };
struct LiteHeightParseResult : LiteParseResultBase { LiteHeightResponse height; };
struct LiteBalanceParseResult : LiteParseResultBase { LiteBalanceResponse balance; };
struct LiteAddressesParseResult : LiteParseResultBase { LiteAddressesResponse addresses; };
struct LiteNotesParseResult : LiteParseResultBase { LiteNotesResponse notes; };
struct LiteTransactionsParseResult : LiteParseResultBase { LiteTransactionsResponse transactions; };
struct LiteSyncStatusParseResult : LiteParseResultBase { LiteSyncStatusResponse syncStatus; };
const char* liteResultCommandName(LiteResultCommand command);
const char* liteResultParserErrorName(LiteResultParserError error);
const char* liteSpendableOutputKindName(LiteSpendableOutputKind kind);
const char* liteTransactionDirectionName(LiteTransactionDirection direction);
LiteInfoParseResult parseLiteInfoResponse(const std::string& jsonText);
LiteInfoParseResult parseLiteInfoResponse(const nlohmann::json& value);
LiteHeightParseResult parseLiteHeightResponse(const std::string& jsonText);
LiteHeightParseResult parseLiteHeightResponse(const nlohmann::json& value);
LiteBalanceParseResult parseLiteBalanceResponse(const std::string& jsonText);
LiteBalanceParseResult parseLiteBalanceResponse(const nlohmann::json& value);
LiteAddressesParseResult parseLiteAddressesResponse(const std::string& jsonText);
LiteAddressesParseResult parseLiteAddressesResponse(const nlohmann::json& value);
LiteNotesParseResult parseLiteNotesResponse(const std::string& jsonText);
LiteNotesParseResult parseLiteNotesResponse(const nlohmann::json& value);
LiteTransactionsParseResult parseLiteTransactionsResponse(const std::string& jsonText);
LiteTransactionsParseResult parseLiteTransactionsResponse(const nlohmann::json& value);
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const std::string& jsonText);
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const nlohmann::json& value);
// A backend `sync` / `rescan` reply. Success is exactly a JSON object with result == "success" (both do_sync Ok paths
// and do_rescan emit it). Anything else is the failure text of do_sync/do_rescan (plain text, NO "Error:" prefix), e.g.
// "Error getting latest block ...", "Server's latest block(..) is behind ours(..)", "Reorg has exceeded 100 blocks. Aborting."
struct LiteSyncOpResult {
bool ok = false;
bool terminal = false; // retrying cannot help (reorg deeper than the wallet can unwind)
std::string error; // trimmed, <= 300 chars; empty when ok
};
LiteSyncOpResult parseLiteSyncOpResponse(const std::string& text);
// Wait before sync-retry number `attempt` (1-based): baseMs, x2 per attempt, capped at 60000 ms.
int liteSyncRetryDelayMs(int attempt, int baseMs);
} // namespace dragonx::wallet