The background 2 s refresh worker re-queried and re-copied the whole wallet every cycle (a fresh TLS refresh plus a multi-megabyte JSON build, copies and DOM parse), even when nothing had changed, so CPU and the network never went idle. The worker now skips the full refresh and the re-publish on a cycle that is provably unchanged, while never letting the UI show stale confirmed balance or transactions: - The lite bridge keeps a monotonic mutation epoch, bumped after any command not on a read-only allow-list, so a local change made on any thread (send, shield, import, new address, encrypt/unlock/lock, console) is detected at one choke point. - Each cycle the worker runs one cheap sync probe (one connection, the mandatory block ingest) and a local height read, and refreshes only when the scanned height advanced, the mutation epoch moved, the encryption state changed, a sync is in progress, or a ~10 s heartbeat elapsed; otherwise it skips. Direct/synchronous callers are not gated. - A failed probe backs off instead of running a second sync, so a dead server is not retried back-to-back. Confirmed funds can never be stale beyond one cycle (every confirmed change advances the height or bumps the epoch); only unconfirmed mempool rows can lag up to the heartbeat, and the spend path re-queries the backend directly rather than reading the gated model. Average idle cost drops from three data connections per cycle to about one. Tests: a bridge mutation-epoch test and a worker change-gate test (skip-when-unchanged; refresh on height advance, local mutation and encryption change) against the fake backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
3.9 KiB
C++
95 lines
3.9 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
|
|
namespace dragonx {
|
|
namespace wallet {
|
|
|
|
struct LiteBridgeStringResult {
|
|
bool ok = false;
|
|
std::string value;
|
|
std::string error;
|
|
};
|
|
|
|
struct LiteClientBridgeApi {
|
|
using WalletExistsFn = bool (*)(const char* chainName);
|
|
using InitializeNewFn = char* (*)(bool dangerous, const char* server);
|
|
using InitializeNewFromPhraseFn = char* (*)(bool dangerous,
|
|
const char* server,
|
|
const char* seed,
|
|
unsigned long long birthday,
|
|
unsigned long long account,
|
|
bool overwrite);
|
|
using InitializeExistingFn = char* (*)(bool dangerous, const char* server);
|
|
using ExecuteFn = char* (*)(const char* command, const char* args);
|
|
using FreeStringFn = void (*)(char* value);
|
|
using CheckServerOnlineFn = bool (*)(const char* server);
|
|
using ShutdownFn = void (*)();
|
|
|
|
WalletExistsFn walletExists = nullptr;
|
|
InitializeNewFn initializeNew = nullptr;
|
|
InitializeNewFromPhraseFn initializeNewFromPhrase = nullptr;
|
|
InitializeExistingFn initializeExisting = nullptr;
|
|
ExecuteFn execute = nullptr;
|
|
FreeStringFn freeString = nullptr;
|
|
CheckServerOnlineFn checkServerOnline = nullptr;
|
|
ShutdownFn shutdown = nullptr;
|
|
};
|
|
|
|
class LiteClientBridge {
|
|
public:
|
|
static LiteClientBridge unavailable(std::string reason);
|
|
static LiteClientBridge fromApi(LiteClientBridgeApi api);
|
|
static LiteClientBridge linkedSdxl();
|
|
|
|
LiteClientBridge(const LiteClientBridge&) = delete;
|
|
LiteClientBridge& operator=(const LiteClientBridge&) = delete;
|
|
LiteClientBridge(LiteClientBridge&& other) noexcept;
|
|
LiteClientBridge& operator=(LiteClientBridge&& other) noexcept;
|
|
~LiteClientBridge();
|
|
|
|
bool available() const;
|
|
const std::string& unavailableReason() const { return unavailableReason_; }
|
|
|
|
bool walletExists(const std::string& chainName) const;
|
|
bool checkServerOnline(const std::string& server) const;
|
|
|
|
LiteBridgeStringResult initializeNew(bool dangerous, const std::string& server);
|
|
LiteBridgeStringResult initializeNewFromPhrase(bool dangerous,
|
|
const std::string& server,
|
|
const std::string& seed,
|
|
unsigned long long birthday,
|
|
unsigned long long account,
|
|
bool overwrite);
|
|
LiteBridgeStringResult initializeExisting(bool dangerous, const std::string& server);
|
|
LiteBridgeStringResult execute(const std::string& command, const std::string& args);
|
|
void shutdown();
|
|
|
|
// Monotonic counter bumped AFTER every execute() of a command that could change wallet-visible
|
|
// data (anything not on the read-only allow-list). The refresh worker samples this to detect
|
|
// local mutations made on ANY thread without hooking each controller method. Compare-only (never
|
|
// cleared): the worker stores a baseline and refreshes whenever it differs, so a mutation can
|
|
// never be "consumed"/lost in a race.
|
|
std::uint64_t mutationEpoch() const { return mutationEpoch_.load(std::memory_order_acquire); }
|
|
|
|
private:
|
|
LiteClientBridge(LiteClientBridgeApi api, std::string unavailableReason);
|
|
|
|
LiteBridgeStringResult unavailableResult() const;
|
|
LiteBridgeStringResult takeOwnedString(char* rawValue) const;
|
|
static bool commandIsReadOnly(const std::string& command);
|
|
|
|
LiteClientBridgeApi api_;
|
|
std::string unavailableReason_;
|
|
bool shutdownCalled_ = false;
|
|
std::atomic<std::uint64_t> mutationEpoch_{0};
|
|
};
|
|
|
|
} // namespace wallet
|
|
} // namespace dragonx
|