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>
182 lines
5.9 KiB
C++
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
|