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:
2026-06-04 21:15:28 -05:00
parent a78a13edf3
commit 863d015628
69 changed files with 39458 additions and 85 deletions

View File

@@ -62,19 +62,8 @@ namespace {
bool isPageEnabledForBuild(ui::NavPage page)
{
#if DRAGONX_LITE_BUILD
switch (page) {
case ui::NavPage::Console:
case ui::NavPage::Peers:
case ui::NavPage::Explorer:
return false;
default:
return true;
}
#else
(void)page;
return true;
#endif
return wallet::isUiSurfaceAvailable(
wallet::currentWalletCapabilities(), ui::NavPageSurface(page));
}
std::string unencryptedTransactionHistoryCacheKey(const std::string& walletIdentity)
@@ -204,7 +193,7 @@ void App::tryConnect()
connection_status_ = TR("sb_no_conf");
// Try to start embedded daemon if enabled
if (use_embedded_daemon_ && !isEmbeddedDaemonRunning()) {
if (isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
connection_status_ = TR("sb_starting_daemon");
if (startEmbeddedDaemon()) {
// Will retry connection after daemon starts
@@ -222,7 +211,7 @@ void App::tryConnect()
daemon_controller_ ? daemon_controller_->lastError().c_str() : "(no daemon object)",
daemon::EmbeddedDaemon::findDaemonBinary().c_str());
}
} else if (!use_embedded_daemon_) {
} else if (!isUsingEmbeddedDaemon()) {
VERBOSE_LOGF("[connect #%d] Embedded daemon disabled (using external). No config found at %s\n",
connect_attempt, confPath.c_str());
}
@@ -273,7 +262,7 @@ void App::tryConnect()
daemon_controller_->lastError().empty() ? "(none)" : daemon_controller_->lastError().c_str());
} else {
VERBOSE_LOGF("[connect #%d] No embedded daemon object (use_embedded=%s)\n",
attempt, use_embedded_daemon_ ? "yes" : "no");
attempt, isUsingEmbeddedDaemon() ? "yes" : "no");
}
worker_->post([this, config, daemonStarting, externalDetected, attempt]() -> rpc::RPCWorker::MainCb {
@@ -377,7 +366,7 @@ void App::tryConnect()
onDisconnected("Connection failed");
VERBOSE_LOGF("[connect #%d] RPC connection failed — no daemon starting, no external detected\n", attempt);
if (use_embedded_daemon_ && !isEmbeddedDaemonRunning()) {
if (isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
// Prevent infinite crash-restart loop
if (daemon_controller_ && daemon_controller_->crashCount() >= 3) {
{ char buf[128]; snprintf(buf, sizeof(buf), TR("sb_daemon_crashed"), daemon_controller_->crashCount());
@@ -397,7 +386,7 @@ void App::tryConnect()
daemon_controller_ ? daemon_controller_->lastError().c_str() : "(no daemon object)");
}
}
} else if (!use_embedded_daemon_) {
} else if (!isUsingEmbeddedDaemon()) {
VERBOSE_LOGF("[connect #%d] Embedded daemon disabled — external daemon at %s:%s not responding\n",
attempt, config.host.c_str(), config.port.c_str());
} else {
@@ -1392,11 +1381,11 @@ void App::refreshMarketData()
void App::startMining(int threads)
{
#if DRAGONX_LITE_BUILD
(void)threads;
ui::Notifications::instance().warning("Solo mining is unavailable in lite build");
return;
#endif
if (!supportsSoloMining()) {
(void)threads;
ui::Notifications::instance().warning("Solo mining is unavailable in lite build");
return;
}
if (!state_.connected || !rpc_ || !worker_) return;
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
@@ -1426,9 +1415,7 @@ void App::startMining(int threads)
void App::stopMining()
{
#if DRAGONX_LITE_BUILD
return;
#endif
if (!supportsSoloMining()) return;
if (!state_.connected || !rpc_ || !worker_) return;
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
@@ -1454,6 +1441,12 @@ void App::stopMining()
void App::startPoolMining(int threads)
{
if (!supportsPoolMining()) {
(void)threads;
ui::Notifications::instance().warning("Pool mining is unavailable in this build");
return;
}
if (!xmrig_manager_)
xmrig_manager_ = std::make_unique<daemon::XmrigManager>();

View File

@@ -616,7 +616,7 @@ void App::checkIdleMining() {
if (idle_mining_active_) {
idle_mining_active_ = false;
idle_scaled_to_idle_ = false;
if (settings_ && settings_->getPoolMode()) {
if (settings_ && (settings_->getPoolMode() || !supportsSoloMining())) {
if (xmrig_manager_ && xmrig_manager_->isRunning())
stopPoolMining();
} else {
@@ -634,7 +634,8 @@ void App::checkIdleMining() {
int idleSec = util::Platform::getSystemIdleSeconds();
int delay = settings_->getMineIdleDelay();
bool isPool = settings_->getPoolMode();
bool isPool = settings_->getPoolMode() || !supportsSoloMining();
if (isPool && !supportsPoolMining()) return;
bool threadScaling = settings_->getIdleThreadScaling();
int maxThreads = std::max(1, (int)std::thread::hardware_concurrency());

View File

@@ -72,6 +72,11 @@ WizardUiState s_wizardUi;
void App::restartWizard()
{
if (!supportsFullNodeLifecycleActions()) {
ui::Notifications::instance().warning("Lite wallet lifecycle requests are available from Settings as dry-run readiness checks");
return;
}
DEBUG_LOGF("[App] Restarting setup wizard — stopping daemon...\n");
// Reset crash counter for fresh wizard attempt
@@ -804,6 +809,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ImGui::Button("Retry##bs", ImVec2(btnW2, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
stopDaemonForBootstrap();
@@ -812,6 +818,7 @@ void App::renderFirstRunWizard() {
bootstrap_->start(dataDir);
wizard_phase_ = WizardPhase::BootstrapInProgress;
}
ImGui::EndDisabled();
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
@@ -985,6 +992,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ImGui::Button("Download##bs", ImVec2(dlBtnW, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
stopDaemonForBootstrap();
@@ -993,6 +1001,7 @@ void App::renderFirstRunWizard() {
bootstrap_->start(dataDir);
wizard_phase_ = WizardPhase::BootstrapInProgress;
}
ImGui::EndDisabled();
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
@@ -1002,6 +1011,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ImGui::Button("Mirror##bs_mirror", ImVec2(mirrorW, btnH2))) {
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
@@ -1010,6 +1020,7 @@ void App::renderFirstRunWizard() {
bootstrap_->start(dataDir, mirrorUrl);
wizard_phase_ = WizardPhase::BootstrapInProgress;
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.");
}

1855
src/chat/chat_protocol.cpp Normal file

File diff suppressed because it is too large Load Diff

586
src/chat/chat_protocol.h Normal file
View File

@@ -0,0 +1,586 @@
#pragma once
#include <cstddef>
#include <string>
#include <vector>
#ifndef DRAGONX_ENABLE_CHAT
#define DRAGONX_ENABLE_CHAT 0
#endif
namespace dragonx::chat {
enum class HushChatHeaderType {
Message,
ContactRequest
};
struct HushChatHeader {
int header_number = 0;
int version = 0;
std::string reply_zaddr;
std::string conversation_id;
HushChatHeaderType type = HushChatHeaderType::Message;
std::string secretstream_header_hex;
std::string public_key_hex;
};
struct HushChatHeaderParseResult {
bool ok = false;
HushChatHeader header;
std::string error;
};
struct HushChatMemoOutput {
std::size_t position = 0;
std::string memo;
};
struct HushChatMemoPair {
HushChatHeader header;
std::size_t header_position = 0;
std::size_t payload_position = 0;
std::string payload_memo;
};
enum class HushChatMemoGroupingIssue {
InvalidHeader,
MissingPayload,
DuplicateHeader,
OversizedMemo
};
struct HushChatMemoGroupingIssueInfo {
HushChatMemoGroupingIssue issue = HushChatMemoGroupingIssue::InvalidHeader;
std::size_t position = 0;
std::string detail;
};
struct HushChatMemoGroupingResult {
std::vector<HushChatMemoPair> pairs;
std::vector<HushChatMemoGroupingIssueInfo> issues;
std::size_t ignored_memo_count = 0;
};
struct HushChatTransactionInput {
std::string txid;
std::vector<HushChatMemoOutput> outputs;
};
struct HushChatTransactionMetadata {
std::string txid;
HushChatHeaderType type = HushChatHeaderType::Message;
std::string conversation_id;
std::string reply_zaddr;
std::size_t header_position = 0;
std::size_t payload_position = 0;
std::size_t payload_size = 0;
};
struct HushChatTransactionExtractionResult {
bool feature_enabled = false;
std::vector<HushChatTransactionMetadata> metadata;
std::vector<HushChatMemoGroupingIssueInfo> issues;
std::size_t ignored_memo_count = 0;
};
enum class HushChatDecryptPreflightError {
None,
FeatureDisabled,
NonMessageHeader,
InvalidHeaderNumber,
UnsupportedVersion,
MissingReplyAddress,
MissingConversationId,
InvalidSecretstreamHeader,
InvalidPublicKey,
EmptyCiphertext,
OversizedCiphertext,
OddLengthCiphertext,
InvalidCiphertextHex,
TruncatedCiphertext
};
struct HushChatDecryptPreflightInput {
HushChatHeader header;
std::string ciphertext_hex;
};
struct HushChatDecryptPreflightResult {
bool ok = false;
bool feature_enabled = false;
HushChatDecryptPreflightError error = HushChatDecryptPreflightError::None;
const char* error_name = "None";
std::size_t ciphertext_size = 0;
};
enum class HushChatHexDecodeError {
None,
Empty,
OddLength,
InvalidHex,
UnexpectedByteLength
};
struct HushChatHexDecodeResult {
bool ok = false;
HushChatHexDecodeError error = HushChatHexDecodeError::None;
const char* error_name = "None";
std::vector<unsigned char> bytes;
};
enum class HushChatDecryptDirection {
Incoming,
Outgoing
};
enum class HushChatSessionKeySelection {
ClientRx,
ServerTx
};
enum class HushChatDecryptInputError {
None,
FeatureDisabled,
InvalidStoredChatKey,
DecryptPreflightFailed,
InvalidPeerPublicKey,
InvalidStreamHeader,
InvalidCiphertext
};
struct HushChatDecryptInputMaterial {
std::string stored_chat_key_hex;
HushChatHeader header;
std::string ciphertext_hex;
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
std::string peer_public_key_hex;
};
struct HushChatPreparedDecryptInput {
std::vector<unsigned char> stored_chat_key_bytes;
std::vector<unsigned char> seed_bytes;
std::vector<unsigned char> peer_public_key_bytes;
std::vector<unsigned char> stream_header_bytes;
std::vector<unsigned char> ciphertext_bytes;
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx;
std::size_t plaintext_capacity = 0;
};
struct HushChatDecryptInputPreparationResult {
bool ok = false;
bool feature_enabled = false;
HushChatDecryptInputError error = HushChatDecryptInputError::None;
const char* error_name = "None";
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None;
HushChatPreparedDecryptInput prepared;
};
struct HushChatDecryptFixtureReadinessResult {
bool ready = false;
std::size_t stored_chat_key_size = 0;
std::size_t seed_size = 0;
std::size_t peer_public_key_size = 0;
std::size_t stream_header_size = 0;
std::size_t ciphertext_size = 0;
std::size_t plaintext_capacity = 0;
HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx;
};
enum class HushChatCompatibilityFixtureError {
None,
FeatureDisabled,
MissingFixtureId,
InvalidLocalPublicKey,
InvalidPeerPublicKey,
InvalidHeaderMemo,
InvalidMemoPair,
NonMemoHeader,
HeaderPublicKeyMismatch,
DecryptInputFailed,
NotFixtureReady,
ExpectedStoredChatKeyLengthMismatch,
ExpectedSeedLengthMismatch,
ExpectedLocalPublicKeyLengthMismatch,
ExpectedPeerPublicKeyLengthMismatch,
ExpectedStreamHeaderLengthMismatch,
ExpectedCiphertextLengthMismatch,
ExpectedPlaintextLengthMismatch,
ExpectedRoleMismatch,
InvalidPlaintextHash
};
struct HushChatCompatibilityFixture {
std::string fixture_id;
std::string stored_chat_key_hex;
std::string local_public_key_hex;
std::string peer_public_key_hex;
std::string header_memo;
std::string ciphertext_memo;
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
HushChatSessionKeySelection expected_session_key_selection = HushChatSessionKeySelection::ClientRx;
std::size_t expected_stored_chat_key_size = 32;
std::size_t expected_seed_size = 32;
std::size_t expected_local_public_key_size = 32;
std::size_t expected_peer_public_key_size = 32;
std::size_t expected_stream_header_size = 24;
std::size_t expected_ciphertext_size = 0;
std::size_t expected_plaintext_size = 0;
std::string expected_plaintext_hash_hex;
};
struct HushChatCompatibilityFixtureVerificationResult {
bool ok = false;
bool feature_enabled = false;
HushChatCompatibilityFixtureError error = HushChatCompatibilityFixtureError::None;
const char* error_name = "None";
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
HushChatDecryptInputError decrypt_input_error = HushChatDecryptInputError::None;
HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None;
HushChatHeader header;
HushChatDecryptInputPreparationResult preparation;
HushChatDecryptFixtureReadinessResult readiness;
std::size_t local_public_key_size = 0;
std::size_t peer_public_key_size = 0;
std::size_t plaintext_hash_size = 0;
};
enum class HushChatCompatibilityFixtureKind {
IncomingMemo,
OutgoingMemo,
SeedPublicKeyProjection,
CorruptedAuthFailure,
ContactExclusion
};
enum class HushChatCompatibilityFixtureFileStatus {
Pending,
Ready
};
enum class HushChatCompatibilityFixtureFileError {
None,
FeatureDisabled,
InvalidJson,
JsonNotObject,
InvalidSchema,
MissingKind,
UnknownKind,
MissingStatus,
UnknownStatus,
MissingFixtureId,
MissingPendingReason,
MissingFixtureObject,
InvalidFixtureField,
FixtureVerificationFailed,
ContactFixtureNotExcluded,
FileReadFailed
};
struct HushChatCompatibilityFixtureFile {
std::string schema;
HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo;
HushChatCompatibilityFixtureFileStatus status = HushChatCompatibilityFixtureFileStatus::Pending;
std::string fixture_id;
std::string pending_reason;
HushChatCompatibilityFixture fixture;
};
struct HushChatCompatibilityFixtureFileParseResult {
bool ok = false;
bool feature_enabled = false;
bool pending = false;
bool verified = false;
bool excluded_from_decrypt = false;
HushChatCompatibilityFixtureFileError error = HushChatCompatibilityFixtureFileError::None;
const char* error_name = "None";
HushChatCompatibilityFixtureFile file;
HushChatCompatibilityFixtureVerificationResult verification;
};
enum class HushChatSeedPublicKeyProjectionError {
None,
FeatureDisabled,
MissingFixtureId,
InvalidStoredChatKey,
InvalidLocalPublicKey,
ExpectedStoredChatKeyLengthMismatch,
ExpectedSeedLengthMismatch,
ExpectedLocalPublicKeyLengthMismatch,
SodiumInitializationFailed,
KeypairProjectionFailed,
ProjectedPublicKeyMismatch
};
struct HushChatSeedPublicKeyProjectionResult {
bool ok = false;
bool feature_enabled = false;
HushChatSeedPublicKeyProjectionError error = HushChatSeedPublicKeyProjectionError::None;
const char* error_name = "None";
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
std::size_t stored_chat_key_size = 0;
std::size_t seed_size = 0;
std::size_t local_public_key_size = 0;
std::size_t projected_public_key_size = 0;
};
enum class HushChatCorruptedAuthFailureReadinessError {
None,
FeatureDisabled,
FixturePending,
WrongFixtureKind,
FixtureNotVerified,
SeedProjectionNotVerified
};
struct HushChatCorruptedAuthFailureReadinessResult {
bool ok = false;
bool feature_enabled = false;
bool structurally_ready_for_future_auth_check = false;
bool requires_future_secretstream_auth_failure = false;
bool decrypted = false;
bool authenticated = false;
HushChatCorruptedAuthFailureReadinessError error = HushChatCorruptedAuthFailureReadinessError::None;
const char* error_name = "None";
};
enum class HushChatCompatibilityFixtureImportError {
None,
FeatureDisabled,
MissingRequiredKind,
DuplicateKind,
FixtureLoadFailed,
FixtureKindMismatch,
FixturePending,
FixtureInvalid,
FixtureNotVerified,
SeedProjectionFailed,
AuthFailureScaffoldFailed,
ContactFixtureNotExcluded
};
struct HushChatCompatibilityFixtureImportCandidate {
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
std::string path;
};
struct HushChatCompatibilityFixtureImportItem {
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
std::string path;
bool supplied = false;
bool pending = false;
bool replacement_eligible = false;
bool seed_projection_verified = false;
bool future_auth_failure_required = false;
bool structurally_ready_for_future_auth_check = false;
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
const char* error_name = "None";
HushChatCompatibilityFixtureFileParseResult parsed;
HushChatSeedPublicKeyProjectionResult seed_projection;
HushChatCorruptedAuthFailureReadinessResult auth_failure_readiness;
};
struct HushChatCompatibilityFixtureImportChecklistResult {
bool ok = false;
bool feature_enabled = false;
bool replacement_ready = false;
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
const char* error_name = "None";
std::size_t required_count = 0;
std::size_t supplied_count = 0;
std::size_t missing_count = 0;
std::size_t pending_count = 0;
std::size_t verified_count = 0;
std::size_t seed_projection_verified_count = 0;
std::size_t future_auth_failure_required_count = 0;
std::size_t auth_failure_structural_ready_count = 0;
std::size_t excluded_count = 0;
std::size_t rejected_count = 0;
std::vector<HushChatCompatibilityFixtureImportItem> items;
};
struct HushChatCompatibilityFixtureReplacementReportItem {
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
std::string path;
bool supplied = false;
bool pending = false;
bool replacement_eligible = false;
bool refused = true;
bool seed_projection_verified = false;
bool future_auth_failure_required = false;
bool structurally_ready_for_future_auth_check = false;
bool cont_excluded = false;
bool decrypted = false;
bool authenticated = false;
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
const char* error_name = "None";
};
struct HushChatCompatibilityFixtureReplacementDryRunResult {
bool ok = false;
bool feature_enabled = false;
bool dry_run_only = true;
bool redacted_report = true;
bool would_replace = false;
bool replacement_refused = true;
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
const char* error_name = "None";
std::size_t required_count = 0;
std::size_t supplied_count = 0;
std::size_t missing_count = 0;
std::size_t pending_count = 0;
std::size_t verified_count = 0;
std::size_t seed_projection_verified_count = 0;
std::size_t future_auth_failure_required_count = 0;
std::size_t auth_failure_structural_ready_count = 0;
std::size_t excluded_count = 0;
std::size_t rejected_count = 0;
std::vector<HushChatCompatibilityFixtureReplacementReportItem> report_items;
};
enum class HushChatCaptureManifestError {
None,
FeatureDisabled,
FileReadFailed,
InvalidJson,
JsonNotObject,
InvalidSchema,
MissingManifestId,
MissingStatus,
UnknownStatus,
MissingFixtureDirectory,
MissingDryRunCommand,
InvalidDryRunCommand,
MissingProvenance,
MissingSourceClient,
InvalidSourceClient,
MissingSourceClientVersion,
MissingCaptureDate,
MissingNetwork,
MissingCaptureMethod,
MissingHandling,
MissingHandlingFlag,
HandlingFlagNotTrue,
MissingCategories,
InvalidCategoryEntry,
UnknownCategory,
DuplicateCategory,
MissingRequiredCategory,
ProhibitedFieldPresent
};
enum class HushChatCaptureManifestStatus {
Staged
};
struct HushChatCaptureManifestCategoryReport {
HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo;
std::string staged_filename;
bool declared = false;
};
struct HushChatCaptureManifestValidationResult {
bool ok = false;
bool feature_enabled = false;
bool redacted_report = true;
bool validates_provenance_only = true;
bool no_sensitive_material_declared = false;
bool has_dry_run_command = false;
HushChatCaptureManifestError error = HushChatCaptureManifestError::None;
const char* error_name = "None";
HushChatCaptureManifestStatus status = HushChatCaptureManifestStatus::Staged;
std::string manifest_path;
std::string fixture_directory;
std::size_t required_count = 0;
std::size_t declared_count = 0;
std::size_t missing_count = 0;
std::size_t duplicate_count = 0;
std::size_t prohibited_field_count = 0;
std::size_t handling_flag_count = 0;
std::vector<HushChatCaptureManifestCategoryReport> categories;
};
constexpr int kHushChatSupportedVersion = 0;
constexpr std::size_t kHushChatMemoByteLimit = 512;
constexpr std::size_t kHushChatPublicKeyHexLength = 64;
constexpr std::size_t kHushChatSecretstreamHeaderHexLength = 48;
constexpr std::size_t kHushChatSecretstreamABytes = 17;
constexpr std::size_t kHushChatStoredChatKeyByteLength = 32;
constexpr std::size_t kHushChatStoredChatKeyHexLength = kHushChatStoredChatKeyByteLength * 2;
constexpr std::size_t kHushChatSeedByteLength = 32;
constexpr std::size_t kHushChatPublicKeyByteLength = kHushChatPublicKeyHexLength / 2;
constexpr std::size_t kHushChatSecretstreamHeaderByteLength = kHushChatSecretstreamHeaderHexLength / 2;
constexpr const char* kHushChatCompatibilityFixtureSchema = "dragonx.hushchat.compat-fixture.v1";
constexpr const char* kHushChatCaptureManifestSchema = "dragonx.hushchat.capture-manifest.v1";
constexpr bool hushChatFeatureEnabledAtBuild()
{
return DRAGONX_ENABLE_CHAT != 0;
}
HushChatHeaderParseResult parseHushChatHeaderMemo(const std::string& memo);
HushChatMemoGroupingResult groupHushChatMemoOutputs(const std::vector<HushChatMemoOutput>& outputs);
HushChatTransactionExtractionResult extractHushChatTransactionMetadata(
const HushChatTransactionInput& transaction,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight(
const HushChatDecryptPreflightInput& input,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex,
std::size_t expectedByteLength);
HushChatDecryptInputPreparationResult prepareHushChatDecryptInput(
const HushChatDecryptInputMaterial& material,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness(
const HushChatPreparedDecryptInput& prepared);
HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction);
HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture(
const HushChatCompatibilityFixture& fixture,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile(
const std::string& jsonText,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile(
const std::string& path,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection(
const HushChatCompatibilityFixture& fixture,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness(
const HushChatCompatibilityFixtureFileParseResult& parsed,
const HushChatSeedPublicKeyProjectionResult& seedProjection,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
std::vector<HushChatCompatibilityFixtureKind> hushChatRequiredCompatibilityFixtureKinds();
HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist(
const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun(
const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCaptureManifestValidationResult validateHushChatCaptureManifest(
const std::string& jsonText,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile(
const std::string& path,
bool featureEnabled = hushChatFeatureEnabledAtBuild());
const char* hushChatHeaderTypeName(HushChatHeaderType type);
const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue);
const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error);
const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error);
const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction);
const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection);
const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error);
const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error);
const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind);
const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status);
const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error);
const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error);
const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error);
const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error);
const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error);
} // namespace dragonx::chat

View File

@@ -1,31 +1,3 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
#define DRAGONX_VERSION "1.2.0-rc1"
#define DRAGONX_VERSION_MAJOR 1
#define DRAGONX_VERSION_MINOR 2
#define DRAGONX_VERSION_PATCH 0
#define DRAGONX_APP_NAME "ObsidianDragonLite"
#define DRAGONX_ORG_NAME "Hush"
// Default RPC settings
#define DRAGONX_DEFAULT_RPC_HOST "127.0.0.1"
#define DRAGONX_DEFAULT_RPC_PORT "21769"
// Coin parameters
#define DRAGONX_TICKER "DRGX"
#define DRAGONX_COIN_NAME "DragonX"
#define DRAGONX_URI_SCHEME "drgx"
#define DRAGONX_ZATOSHI_PER_COIN 100000000
#define DRAGONX_DEFAULT_FEE 0.0001
// Config file names
#define DRAGONX_CONF_FILENAME "DRAGONX.conf"
#define DRAGONX_WALLET_FILENAME "wallet.dat"
#include "dragonx_generated_version.h"

View File

@@ -4,7 +4,7 @@
#pragma once
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
// !! DO NOT EDIT generated version output — it is generated from version.h.in by CMake.
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
#define DRAGONX_VERSION "@PROJECT_VERSION@@DRAGONX_VERSION_SUFFIX@"

View File

@@ -6,6 +6,7 @@
#include <cstdlib>
#include <map>
#include <set>
#include <unordered_map>
using json = nlohmann::json;
@@ -136,6 +137,60 @@ void appendMissingPreviousTransactions(std::vector<TransactionInfo>& transaction
}
}
using HushChatMemoOutputMap = std::unordered_map<std::string, std::vector<chat::HushChatMemoOutput>>;
std::size_t readMemoPosition(const json& source, std::size_t fallback)
{
for (const char* key : {"position", "outputIndex", "outindex"}) {
auto value = readOptional<int>(source, key);
if (value && *value >= 0) return static_cast<std::size_t>(*value);
}
return fallback;
}
void recordHushChatMemoOutput(HushChatMemoOutputMap* outputs,
const std::string& txid,
const std::string& memo,
std::size_t position)
{
if (!outputs || txid.empty() || memo.empty()) return;
(*outputs)[txid].push_back(chat::HushChatMemoOutput{position, memo});
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const std::string& txid,
const std::vector<chat::HushChatMemoOutput>& outputs)
{
auto extracted = chat::extractHushChatTransactionMetadata(chat::HushChatTransactionInput{txid, outputs});
for (auto& metadata : extracted.metadata) {
destination.push_back(std::move(metadata));
}
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const std::string& txid,
const NetworkRefreshService::TransactionViewCacheEntry& entry)
{
if (!chat::hushChatFeatureEnabledAtBuild()) return;
std::vector<chat::HushChatMemoOutput> outputs;
outputs.reserve(entry.outgoing_outputs.size());
for (const auto& output : entry.outgoing_outputs) {
if (!output.memo.empty()) outputs.push_back(chat::HushChatMemoOutput{output.position, output.memo});
}
appendExtractedHushChatMetadata(destination, txid, outputs);
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const HushChatMemoOutputMap& outputsByTxid)
{
if (!chat::hushChatFeatureEnabledAtBuild()) return;
for (const auto& [txid, outputs] : outputsByTxid) {
appendExtractedHushChatMetadata(destination, txid, outputs);
}
}
} // namespace
NetworkRefreshService::ConnectionInfoResult NetworkRefreshService::parseConnectionInfoResult(const json& info)
@@ -614,17 +669,23 @@ void NetworkRefreshService::appendShieldedReceivedTransactions(std::vector<Trans
std::set<std::string>& knownTxids,
const std::string& address,
const json& received,
const std::set<std::string>& miningAddresses)
const std::set<std::string>& miningAddresses,
HushChatMemoOutputMap* chatMemoOutputs)
{
if (received.is_null() || !received.is_array()) return;
std::size_t fallbackPosition = 0;
for (const auto& note : received) {
const std::size_t notePosition = readMemoPosition(note, fallbackPosition++);
auto txid = readOptional<std::string>(note, "txid");
if (!txid || txid->empty()) continue;
auto change = readOptional<bool>(note, "change");
if (change && *change) continue;
auto memoValue = readOptional<std::string>(note, "memoStr");
if (memoValue) recordHushChatMemoOutput(chatMemoOutputs, *txid, *memoValue, notePosition);
bool dominated = false;
for (const auto& existing : transactions) {
if (existing.txid == *txid && existing.type == "receive") {
@@ -641,7 +702,7 @@ void NetworkRefreshService::appendShieldedReceivedTransactions(std::vector<Trans
if (auto value = readOptional<double>(note, "amount")) info.amount = *value;
if (auto value = readOptional<int>(note, "confirmations")) info.confirmations = *value;
if (auto value = readOptional<std::int64_t>(note, "time")) info.timestamp = *value;
if (auto value = readOptional<std::string>(note, "memoStr")) info.memo = *value;
if (memoValue) info.memo = *memoValue;
knownTxids.insert(*txid);
transactions.push_back(std::move(info));
}
@@ -663,7 +724,9 @@ NetworkRefreshService::TransactionViewCacheEntry NetworkRefreshService::parseVie
}
if (viewTransaction.contains("outputs") && viewTransaction["outputs"].is_array()) {
std::size_t fallbackPosition = 0;
for (const auto& output : viewTransaction["outputs"]) {
const std::size_t outputPosition = readMemoPosition(output, fallbackPosition++);
bool outgoing = false;
if (auto value = readOptional<bool>(output, "outgoing")) outgoing = *value;
if (!outgoing) continue;
@@ -672,6 +735,7 @@ NetworkRefreshService::TransactionViewCacheEntry NetworkRefreshService::parseVie
if (auto value = readOptional<std::string>(output, "address")) out.address = *value;
if (auto value = readOptional<double>(output, "value")) out.value = *value;
if (auto value = readOptional<std::string>(output, "memoStr")) out.memo = *value;
out.position = outputPosition;
entry.outgoing_outputs.push_back(std::move(out));
}
}
@@ -738,6 +802,10 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
result.shieldedScanHeights = snapshot.shieldedScanHeights;
std::set<std::string> knownTxids;
HushChatMemoOutputMap hushChatReceivedOutputs;
HushChatMemoOutputMap* hushChatReceivedOutputsPtr = chat::hushChatFeatureEnabledAtBuild()
? &hushChatReceivedOutputs
: nullptr;
bool transactionRpcError = false;
try {
@@ -779,7 +847,12 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
nextSearchIndex = index + 1;
try {
json received = rpc.call("z_listreceivedbyaddress", json::array({address, 0}));
appendShieldedReceivedTransactions(result.transactions, knownTxids, address, received, snapshot.miningAddresses);
appendShieldedReceivedTransactions(result.transactions,
knownTxids,
address,
received,
snapshot.miningAddresses,
hushChatReceivedOutputsPtr);
if (currentBlockHeight >= 0) result.shieldedScanHeights[address] = currentBlockHeight;
} catch (const std::exception& e) {
transactionRpcError = true;
@@ -817,6 +890,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
if (cached != snapshot.viewTxCache.end()) {
if (!trackedSend || !cached->second.outgoing_outputs.empty()) {
appendViewTransactionOutputs(result.transactions, txid, cached->second);
appendExtractedHushChatMetadata(result.hushChatMetadata, txid, cached->second);
continue;
}
}
@@ -841,6 +915,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
auto entry = parseViewTransactionCacheEntry(viewTransaction);
appendViewTransactionOutputs(result.transactions, txid, entry);
appendExtractedHushChatMetadata(result.hushChatMetadata, txid, entry);
json rawTransaction;
bool hasRawTransaction = false;
@@ -885,6 +960,8 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
}
}
appendExtractedHushChatMetadata(result.hushChatMetadata, hushChatReceivedOutputs);
if (!snapshot.previousTransactions.empty()) {
if (transactionRpcError) {
appendMissingPreviousTransactions(result.transactions, snapshot.previousTransactions, true, snapshot.pendingOpids);
@@ -909,6 +986,10 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
result.transactions = snapshot.previousTransactions;
result.shieldedAddressCount = snapshot.shieldedAddresses.size();
result.shieldedScanHeights = snapshot.shieldedScanHeights;
HushChatMemoOutputMap hushChatReceivedOutputs;
HushChatMemoOutputMap* hushChatReceivedOutputsPtr = chat::hushChatFeatureEnabledAtBuild()
? &hushChatReceivedOutputs
: nullptr;
try {
std::set<std::string> recentTxids;
@@ -944,7 +1025,12 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
std::vector<TransactionInfo> scannedTransactions;
std::set<std::string> scannedTxids;
json received = rpc.call("z_listreceivedbyaddress", json::array({address, 0}));
appendShieldedReceivedTransactions(scannedTransactions, scannedTxids, address, received, snapshot.miningAddresses);
appendShieldedReceivedTransactions(scannedTransactions,
scannedTxids,
address,
received,
snapshot.miningAddresses,
hushChatReceivedOutputsPtr);
for (auto& scanned : scannedTransactions) {
bool replaced = false;
for (auto& existing : result.transactions) {
@@ -967,6 +1053,8 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
(shieldedStart + shieldedLimit >= snapshot.shieldedAddresses.size()) ? 0 : shieldedStart + shieldedLimit;
result.shieldedScanComplete = true;
appendExtractedHushChatMetadata(result.hushChatMetadata, hushChatReceivedOutputs);
sortTransactionsNewestFirst(result.transactions);
return result;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "chat/chat_protocol.h"
#include "data/wallet_state.h"
#include "refresh_scheduler.h"
#include "rpc/rpc_worker.h"
@@ -159,6 +160,7 @@ public:
std::string address;
double value = 0.0;
std::string memo;
std::size_t position = 0;
};
std::vector<Output> outgoing_outputs;
};
@@ -180,6 +182,7 @@ public:
struct TransactionRefreshResult {
std::vector<TransactionInfo> transactions;
std::vector<chat::HushChatTransactionMetadata> hushChatMetadata;
int blockHeight = -1;
TransactionViewCache newViewTxEntries;
std::size_t nextShieldedScanStartIndex = 0;
@@ -260,7 +263,8 @@ public:
std::set<std::string>& knownTxids,
const std::string& address,
const nlohmann::json& received,
const std::set<std::string>& miningAddresses = {});
const std::set<std::string>& miningAddresses = {},
std::unordered_map<std::string, std::vector<chat::HushChatMemoOutput>>* chatMemoOutputs = nullptr);
static TransactionViewCacheEntry parseViewTransactionCacheEntry(const nlohmann::json& viewTransaction);
static void appendViewTransactionOutputs(std::vector<TransactionInfo>& transactions,
const std::string& txid,

View File

@@ -12,6 +12,7 @@
#include "schema/ui_schema.h"
#include "../embedded/IconsMaterialDesign.h"
#include "../util/i18n.h"
#include "../wallet/wallet_capabilities.h"
#include <cstdio>
#include <cmath>
@@ -66,14 +67,26 @@ inline const char* NavSectionLabel(const NavItem& item) {
return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
}
inline wallet::WalletUiSurface NavPageSurface(NavPage page)
{
switch (page) {
case NavPage::Overview: return wallet::WalletUiSurface::Overview;
case NavPage::Send: return wallet::WalletUiSurface::Send;
case NavPage::Receive: return wallet::WalletUiSurface::Receive;
case NavPage::History: return wallet::WalletUiSurface::History;
case NavPage::Mining: return wallet::WalletUiSurface::Mining;
case NavPage::Market: return wallet::WalletUiSurface::Market;
case NavPage::Console: return wallet::WalletUiSurface::Console;
case NavPage::Peers: return wallet::WalletUiSurface::Peers;
case NavPage::Explorer: return wallet::WalletUiSurface::Explorer;
case NavPage::Settings: return wallet::WalletUiSurface::Settings;
default: return wallet::WalletUiSurface::Overview;
}
}
inline bool IsNavPageVisible(NavPage page)
{
#if DRAGONX_LITE_BUILD
return page != NavPage::Console && page != NavPage::Peers && page != NavPage::Explorer;
#else
(void)page;
return true;
#endif
return wallet::isUiSurfaceAvailable(wallet::currentWalletCapabilities(), NavPageSurface(page));
}
// Get the Material Design icon string for a navigation page.

View File

@@ -29,6 +29,7 @@ namespace ui {
class BootstrapDownloadDialog {
public:
static void show(App* app) {
if (!app || !app->supportsFullNodeLifecycleActions()) return;
s_open = true;
s_app = app;
s_state = State::Confirm;
@@ -285,6 +286,7 @@ private:
// ---- Shared: kick off download ----
static void startDownload(const std::string& url) {
if (!s_app || !s_app->supportsFullNodeLifecycleActions()) return;
s_wasDaemonRunning = s_app->stopDaemonForBootstrap();
s_app->setBootstrapDownloading(true);
s_bootstrap = std::make_unique<util::Bootstrap>();

View File

@@ -151,13 +151,11 @@ static void RenderMiningTabContent(App* app)
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
s_pool_state_loaded = true;
}
#if DRAGONX_LITE_BUILD
if (!s_pool_mode) {
if (!app->supportsSoloMining() && !s_pool_mode) {
s_pool_mode = true;
app->settings()->setPoolMode(true);
app->settings()->save();
}
#endif
// Default pool worker to user's first shielded (z) address once available.
// For new wallets without a z-address, leave the field blank so the user
@@ -242,11 +240,11 @@ static void RenderMiningTabContent(App* app)
// MODE TOGGLE — SOLO | POOL segmented control
// ================================================================
{
const bool liteBuild = app->isLiteBuild();
const bool soloMiningAvailable = app->supportsSoloMining();
float toggleW = schema::UI().drawElement("tabs.mining", "mode-toggle-width").size * hs;
float toggleH = schema::UI().drawElement("tabs.mining", "mode-toggle-height").size;
float toggleRnd = schema::UI().drawElement("tabs.mining", "mode-toggle-rounding").size;
float totalW = liteBuild ? toggleW : (toggleW * 2);
float totalW = soloMiningAvailable ? (toggleW * 2) : toggleW;
ImVec2 tMin = ImGui::GetCursorScreenPos();
ImVec2 tMax(tMin.x + totalW, tMin.y + toggleH);
@@ -258,7 +256,7 @@ static void RenderMiningTabContent(App* app)
ImVec2 soloMin = tMin;
ImVec2 soloMax(tMin.x + toggleW, tMax.y);
bool soloHov = material::IsRectHovered(soloMin, soloMax);
if (!liteBuild) {
if (soloMiningAvailable) {
// SOLO button (left half)
if (!s_pool_mode) {
dl->AddRectFilled(soloMin, soloMax, WithAlpha(Primary(), 180), toggleRnd);
@@ -275,7 +273,7 @@ static void RenderMiningTabContent(App* app)
// POOL button (right half) — disabled when solo mining is active
bool soloMiningActive = mining.generate;
ImVec2 poolMin(tMin.x + (liteBuild ? 0.0f : toggleW), tMin.y);
ImVec2 poolMin(tMin.x + (soloMiningAvailable ? toggleW : 0.0f), tMin.y);
ImVec2 poolMax = tMax;
bool poolHov = material::IsRectHovered(poolMin, poolMax);
if (s_pool_mode) {
@@ -296,7 +294,7 @@ static void RenderMiningTabContent(App* app)
}
// Invisible buttons for click targets
if (!liteBuild) {
if (soloMiningAvailable) {
ImGui::SetCursorScreenPos(soloMin);
ImGui::InvisibleButton("##SoloMode", ImVec2(toggleW, toggleH));
if (ImGui::IsItemClicked() && s_pool_mode) {
@@ -310,7 +308,7 @@ static void RenderMiningTabContent(App* app)
ImGui::SetCursorScreenPos(poolMin);
ImGui::InvisibleButton("##PoolMode", ImVec2(toggleW, toggleH));
if (!liteBuild && ImGui::IsItemClicked() && !s_pool_mode && !soloMiningActive) {
if (soloMiningAvailable && ImGui::IsItemClicked() && !s_pool_mode && !soloMiningActive) {
s_pool_mode = true;
app->settings()->setPoolMode(true);
app->settings()->save();
@@ -318,7 +316,7 @@ static void RenderMiningTabContent(App* app)
// so no need to call stopMining() — it would just set the
// toggle-in-progress flag and make the button show "STARTING".
}
if (!liteBuild && poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (soloMiningAvailable && poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (poolHov && soloMiningActive && !s_pool_mode) {
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
}
@@ -1935,7 +1933,7 @@ static void RenderMiningTabContent(App* app)
&& tx.memo.find("Mining Pool payout") != std::string::npos);
if (isSoloMined || isPoolPayout) {
// Apply earnings filter
if (app->isLiteBuild()) {
if (!app->supportsSoloMining()) {
if (s_earnings_filter == 1 && !isPoolPayout) continue;
} else {
if (s_earnings_filter == 1 && !isSoloMined) continue;
@@ -1984,10 +1982,10 @@ static void RenderMiningTabContent(App* app)
// === Earnings filter toggle button (top-right of card) ===
{
const bool liteBuild = app->isLiteBuild();
const char* filterLabels[] = { TR("mining_filter_all"), liteBuild ? TR("mining_pool") : TR("mining_solo"), TR("mining_pool") };
const char* filterIcons[] = { ICON_MD_FUNCTIONS, liteBuild ? ICON_MD_CLOUD : ICON_MD_MEMORY, ICON_MD_CLOUD };
const int filterCount = liteBuild ? 2 : 3;
const bool soloMiningAvailable = app->supportsSoloMining();
const char* filterLabels[] = { TR("mining_filter_all"), soloMiningAvailable ? TR("mining_solo") : TR("mining_pool"), TR("mining_pool") };
const char* filterIcons[] = { ICON_MD_FUNCTIONS, soloMiningAvailable ? ICON_MD_MEMORY : ICON_MD_CLOUD, ICON_MD_CLOUD };
const int filterCount = soloMiningAvailable ? 3 : 2;
const char* curIcon = filterIcons[s_earnings_filter];
const char* curLabel = filterLabels[s_earnings_filter];
@@ -2033,7 +2031,7 @@ static void RenderMiningTabContent(App* app)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
const char* tips[] = {
TR("mining_filter_tip_all"),
liteBuild ? TR("mining_filter_tip_pool") : TR("mining_filter_tip_solo"),
soloMiningAvailable ? TR("mining_filter_tip_solo") : TR("mining_filter_tip_pool"),
TR("mining_filter_tip_pool")
};
ImGui::SetTooltip("%s", tips[s_earnings_filter]);

View File

@@ -0,0 +1,419 @@
#include "wallet/lite_backend_artifact_contract.h"
#include <algorithm>
#include <cctype>
#include <sstream>
#include <utility>
namespace dragonx::wallet {
namespace {
constexpr const char* kSupportedAbiVersion = "sdxl-c-v1";
using Issue = LiteBackendArtifactContractIssue;
using Status = LiteBackendArtifactContractStatus;
void addIssue(LiteBackendArtifactContractResult& result, Issue issue, std::string message)
{
result.issues.push_back({issue, std::move(message)});
if (result.error.empty()) result.error = result.issues.back().message;
}
LiteBackendArtifactContractResult stoppedResult(
LiteBackendArtifactContractResult result,
Status status,
Issue issue,
std::string message)
{
result.status = status;
addIssue(result, issue, std::move(message));
return result;
}
std::string trimCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
bool containsSymbol(const std::vector<std::string>& exportedSymbols, const char* abiName)
{
return std::find(exportedSymbols.begin(), exportedSymbols.end(), abiName) != exportedSymbols.end();
}
bool importedArtifactKindLinkable(LiteBackendArtifactKind kind)
{
return kind == LiteBackendArtifactKind::StaticLibrary || kind == LiteBackendArtifactKind::SharedLibrary;
}
bool signatureTrustMetadataProvided(const LiteBackendArtifactSignatureVerificationMetadata& signature)
{
return !trimCopy(signature.keyFingerprint).empty() || !trimCopy(signature.certificateIdentity).empty();
}
LiteBackendArtifactContractResult validateSignatureMetadata(
LiteBackendArtifactContractResult result,
const LiteBackendArtifactContractInput& input,
LiteBackendArtifactContractOptions options)
{
const auto& signature = input.signatureVerification;
result.signatureRequiredForRelease = signature.requiredForRelease;
const bool shouldValidate = signature.requiredForRelease ||
(options.validateOptionalSignatureMetadata && signature.metadataProvided);
if (!shouldValidate) {
result.signatureMetadataAccepted = true;
result.signatureVerificationForwarded = signature.metadataProvided;
result.signatureVerified = signature.verified;
return result;
}
if (options.requireSignatureMetadataWhenRequired && !signature.policyDefined) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignaturePolicyMissing,
"lite backend artifact signature policy is not defined");
}
if (!signature.metadataProvided) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureMetadataMissing,
"lite backend artifact signature metadata is required but missing");
}
if (trimCopy(signature.signatureFormat).empty() ||
trimCopy(signature.signaturePath).empty() ||
trimCopy(signature.verificationTool).empty() ||
trimCopy(signature.verifiedArtifactSha256).empty()) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureMetadataIncomplete,
"lite backend artifact signature metadata is incomplete");
}
if (!signatureTrustMetadataProvided(signature)) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureTrustMetadataMissing,
"lite backend artifact signature metadata is missing a reviewed trust identity");
}
if (!signature.verificationPerformed) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureVerificationMissing,
"lite backend artifact signature verification has not been performed");
}
if (!signature.verified) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureVerificationFailed,
"lite backend artifact signature verification did not pass");
}
if (!trimCopy(input.artifactSha256).empty() && signature.verifiedArtifactSha256 != input.artifactSha256) {
return stoppedResult(std::move(result), Status::WaitingForSignatureMetadata, Issue::SignatureVerifiedArtifactShaMismatch,
"lite backend artifact signature metadata does not match the artifact SHA-256");
}
result.signatureMetadataAccepted = true;
result.signatureVerificationForwarded = true;
result.signatureVerified = true;
return result;
}
LiteBackendArtifactContractResult rejectRuntimeActions(
LiteBackendArtifactContractResult result,
const LiteBackendArtifactContractInput& input,
LiteBackendArtifactContractOptions options)
{
if (!options.rejectRuntimeActions) return result;
if (input.artifactMutationRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::ArtifactMutationRequested,
"lite backend artifact contract cannot mutate artifacts");
}
if (input.dynamicLibraryLoadRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::DynamicLibraryLoadRequested,
"lite backend artifact contract cannot load dynamic libraries");
}
if (input.dynamicLibraryUnloadRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::DynamicLibraryUnloadRequested,
"lite backend artifact contract cannot unload dynamic libraries");
}
if (input.symbolResolutionRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::SymbolResolutionRequested,
"lite backend artifact contract cannot resolve runtime symbols");
}
if (input.sdxlApiRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::SdxlApiRequested,
"lite backend artifact contract cannot call SDXL APIs");
}
if (input.bridgeCallRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::BridgeCallRequested,
"lite backend artifact contract cannot call the bridge");
}
if (input.serverConnectivityCheckRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::ServerConnectivityCheckRequested,
"lite backend artifact contract cannot check server connectivity");
}
if (input.walletLifecycleRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::WalletLifecycleRequested,
"lite backend artifact contract cannot execute wallet lifecycle flows");
}
if (input.syncRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::SyncRequested,
"lite backend artifact contract cannot start sync");
}
if (input.syncStatusPollingRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::SyncStatusPollingRequested,
"lite backend artifact contract cannot poll syncstatus");
}
if (input.workerQueueRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::WorkerQueueRequested,
"lite backend artifact contract cannot enqueue workers");
}
if (input.walletStateMutationRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::WalletStateMutationRequested,
"lite backend artifact contract cannot mutate WalletState");
}
if (input.walletPersistenceRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::WalletPersistenceRequested,
"lite backend artifact contract cannot persist wallet files");
}
if (input.uploadRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::UploadRequested,
"lite backend artifact contract cannot upload artifacts");
}
if (input.signingRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::SigningRequested,
"lite backend artifact contract cannot sign artifacts");
}
if (input.publicationRequested) {
return stoppedResult(std::move(result), Status::RuntimeActionDisabled, Issue::PublicationRequested,
"lite backend artifact contract cannot publish artifacts");
}
return result;
}
} // namespace
const char* liteBackendArtifactContractSupportedAbiVersion()
{
return kSupportedAbiVersion;
}
const char* liteBackendArtifactContractLinkModeName(LiteBackendArtifactContractLinkMode linkMode)
{
switch (linkMode) {
case LiteBackendArtifactContractLinkMode::ImportedLibrary: return "ImportedLibrary";
case LiteBackendArtifactContractLinkMode::RuntimeDynamicLibrary: return "RuntimeDynamicLibrary";
case LiteBackendArtifactContractLinkMode::ExternalExecutable: return "ExternalExecutable";
}
return "Unknown";
}
const char* liteBackendArtifactContractStatusName(LiteBackendArtifactContractStatus status)
{
switch (status) {
case LiteBackendArtifactContractStatus::ReadyForResolver: return "ReadyForResolver";
case LiteBackendArtifactContractStatus::WaitingForContractOwner: return "WaitingForContractOwner";
case LiteBackendArtifactContractStatus::WaitingForReadOnlyGate: return "WaitingForReadOnlyGate";
case LiteBackendArtifactContractStatus::WaitingForLinkMode: return "WaitingForLinkMode";
case LiteBackendArtifactContractStatus::WaitingForArtifactPath: return "WaitingForArtifactPath";
case LiteBackendArtifactContractStatus::WaitingForArtifactKind: return "WaitingForArtifactKind";
case LiteBackendArtifactContractStatus::WaitingForAbiVersion: return "WaitingForAbiVersion";
case LiteBackendArtifactContractStatus::WaitingForSymbolInventory: return "WaitingForSymbolInventory";
case LiteBackendArtifactContractStatus::WaitingForRequiredSymbols: return "WaitingForRequiredSymbols";
case LiteBackendArtifactContractStatus::WaitingForSignatureMetadata: return "WaitingForSignatureMetadata";
case LiteBackendArtifactContractStatus::RuntimeActionDisabled: return "RuntimeActionDisabled";
}
return "Unknown";
}
const char* liteBackendArtifactContractIssueName(LiteBackendArtifactContractIssue issue)
{
switch (issue) {
#define DRAGONX_CASE(value) case LiteBackendArtifactContractIssue::value: return #value
DRAGONX_CASE(ContractOwnerMissing);
DRAGONX_CASE(ReadOnlyGateMissing);
DRAGONX_CASE(RuntimeDynamicLinkDeferred);
DRAGONX_CASE(ExternalExecutableDeferred);
DRAGONX_CASE(ArtifactPathMissing);
DRAGONX_CASE(ArtifactKindNotLinkable);
DRAGONX_CASE(AbiVersionMissing);
DRAGONX_CASE(AbiVersionUnsupported);
DRAGONX_CASE(SymbolInventoryOwnerMissing);
DRAGONX_CASE(SymbolInventoryMissing);
DRAGONX_CASE(RequiredSymbolMissing);
DRAGONX_CASE(SignaturePolicyMissing);
DRAGONX_CASE(SignatureMetadataMissing);
DRAGONX_CASE(SignatureMetadataIncomplete);
DRAGONX_CASE(SignatureVerificationMissing);
DRAGONX_CASE(SignatureVerificationFailed);
DRAGONX_CASE(SignatureVerifiedArtifactShaMismatch);
DRAGONX_CASE(SignatureTrustMetadataMissing);
DRAGONX_CASE(ArtifactMutationRequested);
DRAGONX_CASE(DynamicLibraryLoadRequested);
DRAGONX_CASE(DynamicLibraryUnloadRequested);
DRAGONX_CASE(SymbolResolutionRequested);
DRAGONX_CASE(SdxlApiRequested);
DRAGONX_CASE(BridgeCallRequested);
DRAGONX_CASE(ServerConnectivityCheckRequested);
DRAGONX_CASE(WalletLifecycleRequested);
DRAGONX_CASE(SyncRequested);
DRAGONX_CASE(SyncStatusPollingRequested);
DRAGONX_CASE(WorkerQueueRequested);
DRAGONX_CASE(WalletStateMutationRequested);
DRAGONX_CASE(WalletPersistenceRequested);
DRAGONX_CASE(UploadRequested);
DRAGONX_CASE(SigningRequested);
DRAGONX_CASE(PublicationRequested);
#undef DRAGONX_CASE
}
return "Unknown";
}
std::vector<LiteBackendArtifactContractSymbol> liteBackendArtifactContractRequiredSymbols()
{
return {
{"walletExists", "litelib_wallet_exists", false, false, true, false, false, false},
{"initializeNew", "litelib_initialize_new", true, false, true, false, false, false},
{"initializeNewFromPhrase", "litelib_initialize_new_from_phrase", true, false, true, false, false, false},
{"initializeExisting", "litelib_initialize_existing", true, false, true, false, false, false},
{"execute", "litelib_execute", true, false, false, true, false, false},
{"freeString", "litelib_rust_free_string", false, true, true, true, false, false},
{"checkServerOnline", "litelib_check_server_online", false, false, false, false, true, false},
{"shutdown", "litelib_shutdown", false, false, true, true, true, true},
};
}
LiteWalletSdxlArtifactSymbolsInput liteBackendArtifactSymbolsFromExportedNames(
const std::vector<std::string>& exportedSymbols)
{
LiteWalletSdxlArtifactSymbolsInput symbols;
symbols.walletExists = containsSymbol(exportedSymbols, "litelib_wallet_exists");
symbols.initializeNew = containsSymbol(exportedSymbols, "litelib_initialize_new");
symbols.initializeNewFromPhrase = containsSymbol(exportedSymbols, "litelib_initialize_new_from_phrase");
symbols.initializeExisting = containsSymbol(exportedSymbols, "litelib_initialize_existing");
symbols.execute = containsSymbol(exportedSymbols, "litelib_execute");
symbols.freeString = containsSymbol(exportedSymbols, "litelib_rust_free_string");
symbols.checkServerOnline = containsSymbol(exportedSymbols, "litelib_check_server_online");
symbols.shutdown = containsSymbol(exportedSymbols, "litelib_shutdown");
return symbols;
}
LiteBackendArtifactContractResult evaluateLiteBackendArtifactContract(
const LiteBackendArtifactContractInput& input,
LiteBackendArtifactContractOptions options)
{
LiteBackendArtifactContractResult result;
result.requiredSymbolCount = liteBackendArtifactContractRequiredSymbols().size();
result.exportedSymbolCount = input.exportedSymbols.size();
result = rejectRuntimeActions(std::move(result), input, options);
if (!result.issues.empty()) return result;
if (options.requireContractOwner && !input.contractOwnerReady) {
return stoppedResult(std::move(result), Status::WaitingForContractOwner, Issue::ContractOwnerMissing,
"lite backend artifact contract owner is not ready");
}
result.contractOwnerAccepted = true;
if (options.requireReadOnlyGate && !input.readOnlyGateReady) {
return stoppedResult(std::move(result), Status::WaitingForReadOnlyGate, Issue::ReadOnlyGateMissing,
"lite backend artifact contract read-only gate is not ready");
}
result.readOnlyGateAccepted = true;
if (options.requireImportedLibraryLinkMode && input.linkMode == LiteBackendArtifactContractLinkMode::RuntimeDynamicLibrary) {
return stoppedResult(std::move(result), Status::WaitingForLinkMode, Issue::RuntimeDynamicLinkDeferred,
"runtime dynamic loading is deferred to the bridge runtime owner phase");
}
if (options.requireImportedLibraryLinkMode && input.linkMode == LiteBackendArtifactContractLinkMode::ExternalExecutable) {
return stoppedResult(std::move(result), Status::WaitingForLinkMode, Issue::ExternalExecutableDeferred,
"external executable bridge mode is not the Phase 1 production link contract");
}
result.importedLinkModeAccepted = input.linkMode == LiteBackendArtifactContractLinkMode::ImportedLibrary;
if (trimCopy(input.artifactPath).empty()) {
return stoppedResult(std::move(result), Status::WaitingForArtifactPath, Issue::ArtifactPathMissing,
"lite backend artifact contract requires an artifact path");
}
result.artifactPathAccepted = true;
if (!importedArtifactKindLinkable(input.artifactKind)) {
return stoppedResult(std::move(result), Status::WaitingForArtifactKind, Issue::ArtifactKindNotLinkable,
"Phase 1 imported link mode requires a static or shared library artifact");
}
result.artifactKindAccepted = true;
if (options.requireAbiVersion && trimCopy(input.abiVersion).empty()) {
return stoppedResult(std::move(result), Status::WaitingForAbiVersion, Issue::AbiVersionMissing,
"lite backend artifact ABI version is missing");
}
if (options.requireAbiVersion && input.abiVersion != kSupportedAbiVersion) {
return stoppedResult(std::move(result), Status::WaitingForAbiVersion, Issue::AbiVersionUnsupported,
"lite backend artifact ABI version is unsupported");
}
result.abiVersionAccepted = true;
result = validateSignatureMetadata(std::move(result), input, options);
if (!result.issues.empty()) return result;
if (options.requireSymbolInventory && !input.symbolInventoryOwnerReady) {
return stoppedResult(std::move(result), Status::WaitingForSymbolInventory, Issue::SymbolInventoryOwnerMissing,
"lite backend artifact symbol inventory owner is not ready");
}
if (options.requireSymbolInventory && input.exportedSymbols.empty()) {
return stoppedResult(std::move(result), Status::WaitingForSymbolInventory, Issue::SymbolInventoryMissing,
"lite backend artifact exported symbol inventory is missing");
}
result.symbolInventoryAccepted = true;
result.symbols = liteBackendArtifactSymbolsFromExportedNames(input.exportedSymbols);
for (const auto& symbol : liteBackendArtifactContractRequiredSymbols()) {
if (!containsSymbol(input.exportedSymbols, symbol.abiName.c_str())) {
result.missingSymbols.push_back(symbol.abiName);
}
}
if (options.requireAllRequiredSymbols && !result.missingSymbols.empty()) {
return stoppedResult(std::move(result), Status::WaitingForRequiredSymbols, Issue::RequiredSymbolMissing,
"lite backend artifact is missing required C ABI symbols");
}
result.requiredSymbolsAccepted = true;
result.resolverCandidate.configured = true;
result.resolverCandidate.artifactPath = input.artifactPath;
result.resolverCandidate.platform = input.platform;
result.resolverCandidate.kind = input.artifactKind;
result.resolverCandidate.versionLabel = input.abiVersion;
result.resolverCandidate.provenance = input.provenance;
result.resolverCandidate.signatureVerification = input.signatureVerification;
result.resolverCandidate.sdxlCompatible = input.sdxlCompatible;
result.resolverCandidate.symbols = result.symbols;
result.resolverCandidate.executableMetadataRequired = false;
result.resolverInput.resolverOwnerReady = true;
result.resolverInput.readOnlyGateReady = true;
result.resolverInput.projectRoot = input.projectRoot;
result.resolverInput.expectedPlatform = input.platform;
result.resolverInput.candidates.push_back(result.resolverCandidate);
result.resolverInputProduced = true;
result.provenanceForwarded = input.provenance.ownerReady && input.provenance.metadataProvided;
result.signatureVerificationForwarded = input.signatureVerification.metadataProvided;
result.sdxlCompatibilityForwarded = input.sdxlCompatible;
result.dynamicLibraryLoadAllowed = false;
result.symbolResolutionAllowed = false;
result.runtimeActivationAllowed = false;
result.ok = true;
result.status = Status::ReadyForResolver;
std::ostringstream summary;
summary << "lite_backend_artifact_contract=ready"
<< ";abi=" << input.abiVersion
<< ";link_mode=" << liteBackendArtifactContractLinkModeName(input.linkMode)
<< ";kind=" << liteBackendArtifactKindName(input.artifactKind)
<< ";symbols=" << result.requiredSymbolCount
<< ";signature_required=" << (result.signatureRequiredForRelease ? "true" : "false")
<< ";signature_verified=" << (result.signatureVerified ? "true" : "false")
<< ";dynamic_loading=false;runtime_activation=false";
result.summary = summary.str();
return result;
}
LiteBackendArtifactResolverInput liteBackendArtifactResolverInputFromContractResult(
const LiteBackendArtifactContractResult& result)
{
return result.resolverInput;
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,195 @@
#pragma once
#include "lite_backend_artifact_resolver.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteBackendArtifactContractLinkMode {
ImportedLibrary,
RuntimeDynamicLibrary,
ExternalExecutable,
};
enum class LiteBackendArtifactContractStatus {
ReadyForResolver,
WaitingForContractOwner,
WaitingForReadOnlyGate,
WaitingForLinkMode,
WaitingForArtifactPath,
WaitingForArtifactKind,
WaitingForAbiVersion,
WaitingForSymbolInventory,
WaitingForRequiredSymbols,
WaitingForSignatureMetadata,
RuntimeActionDisabled,
};
enum class LiteBackendArtifactContractIssue {
ContractOwnerMissing,
ReadOnlyGateMissing,
RuntimeDynamicLinkDeferred,
ExternalExecutableDeferred,
ArtifactPathMissing,
ArtifactKindNotLinkable,
AbiVersionMissing,
AbiVersionUnsupported,
SymbolInventoryOwnerMissing,
SymbolInventoryMissing,
RequiredSymbolMissing,
SignaturePolicyMissing,
SignatureMetadataMissing,
SignatureMetadataIncomplete,
SignatureVerificationMissing,
SignatureVerificationFailed,
SignatureVerifiedArtifactShaMismatch,
SignatureTrustMetadataMissing,
ArtifactMutationRequested,
DynamicLibraryLoadRequested,
DynamicLibraryUnloadRequested,
SymbolResolutionRequested,
SdxlApiRequested,
BridgeCallRequested,
ServerConnectivityCheckRequested,
WalletLifecycleRequested,
SyncRequested,
SyncStatusPollingRequested,
WorkerQueueRequested,
WalletStateMutationRequested,
WalletPersistenceRequested,
UploadRequested,
SigningRequested,
PublicationRequested,
};
struct LiteBackendArtifactContractSymbol {
std::string logicalName;
std::string abiName;
bool returnsOwnedString = false;
bool cleanupFunction = false;
bool requiredForLifecycle = false;
bool requiredForSync = false;
bool requiredForServerCheck = false;
bool requiredForShutdown = false;
};
struct LiteBackendArtifactContractInput {
bool contractOwnerReady = false;
bool readOnlyGateReady = false;
std::string projectRoot;
std::string artifactPath;
LiteBackendArtifactPlatform platform = LiteBackendArtifactPlatform::Current;
LiteBackendArtifactKind artifactKind = LiteBackendArtifactKind::Unknown;
LiteBackendArtifactContractLinkMode linkMode = LiteBackendArtifactContractLinkMode::ImportedLibrary;
std::string abiVersion;
std::string artifactSha256;
LiteBackendArtifactProvenanceMetadata provenance;
LiteBackendArtifactSignatureVerificationMetadata signatureVerification;
bool sdxlCompatible = false;
bool symbolInventoryOwnerReady = false;
std::vector<std::string> exportedSymbols;
bool artifactMutationRequested = false;
bool dynamicLibraryLoadRequested = false;
bool dynamicLibraryUnloadRequested = false;
bool symbolResolutionRequested = false;
bool sdxlApiRequested = false;
bool bridgeCallRequested = false;
bool serverConnectivityCheckRequested = false;
bool walletLifecycleRequested = false;
bool syncRequested = false;
bool syncStatusPollingRequested = false;
bool workerQueueRequested = false;
bool walletStateMutationRequested = false;
bool walletPersistenceRequested = false;
bool uploadRequested = false;
bool signingRequested = false;
bool publicationRequested = false;
};
struct LiteBackendArtifactContractOptions {
bool requireContractOwner = true;
bool requireReadOnlyGate = true;
bool requireImportedLibraryLinkMode = true;
bool requireAbiVersion = true;
bool requireSymbolInventory = true;
bool requireAllRequiredSymbols = true;
bool requireSignatureMetadataWhenRequired = true;
bool validateOptionalSignatureMetadata = true;
bool rejectRuntimeActions = true;
};
struct LiteBackendArtifactContractIssueInfo {
LiteBackendArtifactContractIssue issue = LiteBackendArtifactContractIssue::ContractOwnerMissing;
std::string message;
};
struct LiteBackendArtifactContractResult {
bool ok = false;
bool readOnlyContract = true;
bool noArtifactMutation = true;
bool noDynamicLibraryLoaded = true;
bool noDynamicLibraryUnloaded = true;
bool noSymbolResolutionPerformed = true;
bool noSdxlCalls = true;
bool noBridgeCalls = true;
bool noServerConnectivityChecked = true;
bool noWalletLifecycle = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWorkerQueueEnqueue = true;
bool noWalletStateMutation = true;
bool noWalletPersistence = true;
bool noUpload = true;
bool noSigning = true;
bool noPublication = true;
bool contractOwnerAccepted = false;
bool readOnlyGateAccepted = false;
bool importedLinkModeAccepted = false;
bool artifactPathAccepted = false;
bool artifactKindAccepted = false;
bool abiVersionAccepted = false;
bool provenanceForwarded = false;
bool signatureMetadataAccepted = false;
bool signatureVerificationForwarded = false;
bool signatureRequiredForRelease = false;
bool signatureVerified = false;
bool sdxlCompatibilityForwarded = false;
bool symbolInventoryAccepted = false;
bool requiredSymbolsAccepted = false;
bool resolverInputProduced = false;
bool dynamicLibraryLoadAllowed = false;
bool symbolResolutionAllowed = false;
bool runtimeActivationAllowed = false;
std::size_t requiredSymbolCount = 0;
std::size_t exportedSymbolCount = 0;
LiteBackendArtifactContractStatus status = LiteBackendArtifactContractStatus::WaitingForContractOwner;
LiteWalletSdxlArtifactSymbolsInput symbols;
LiteBackendArtifactCandidate resolverCandidate;
LiteBackendArtifactResolverInput resolverInput;
std::vector<std::string> missingSymbols;
std::vector<LiteBackendArtifactContractIssueInfo> issues;
std::string error;
std::string summary;
};
const char* liteBackendArtifactContractSupportedAbiVersion();
const char* liteBackendArtifactContractLinkModeName(LiteBackendArtifactContractLinkMode linkMode);
const char* liteBackendArtifactContractStatusName(LiteBackendArtifactContractStatus status);
const char* liteBackendArtifactContractIssueName(LiteBackendArtifactContractIssue issue);
std::vector<LiteBackendArtifactContractSymbol> liteBackendArtifactContractRequiredSymbols();
LiteWalletSdxlArtifactSymbolsInput liteBackendArtifactSymbolsFromExportedNames(
const std::vector<std::string>& exportedSymbols);
LiteBackendArtifactContractResult evaluateLiteBackendArtifactContract(
const LiteBackendArtifactContractInput& input,
LiteBackendArtifactContractOptions options = {});
LiteBackendArtifactResolverInput liteBackendArtifactResolverInputFromContractResult(
const LiteBackendArtifactContractResult& result);
} // namespace dragonx::wallet

View File

@@ -0,0 +1,925 @@
#include "wallet/lite_backend_artifact_resolver.h"
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <system_error>
#include <utility>
namespace dragonx::wallet {
namespace {
namespace fs = std::filesystem;
struct CandidateInspectionResult {
bool ok = false;
LiteBackendArtifactResolverStatus status = LiteBackendArtifactResolverStatus::WaitingForArtifactCandidate;
LiteBackendArtifactResolverIssue issue = LiteBackendArtifactResolverIssue::ArtifactMissing;
std::string message;
LiteBackendResolvedArtifact artifact;
LiteWalletSdxlArtifactInput syncArtifactInput;
};
void addIssue(LiteBackendArtifactResolverResult& result,
LiteBackendArtifactResolverIssue issue,
std::string message)
{
result.issues.push_back(LiteBackendArtifactResolverIssueInfo{issue, std::move(message)});
}
void addIssue(LiteBackendActivationReadinessResult& result,
LiteBackendActivationReadinessIssue issue,
std::string message)
{
result.issues.push_back(LiteBackendActivationReadinessIssueInfo{issue, std::move(message)});
}
LiteBackendArtifactResolverResult stoppedResolverResult(
LiteBackendArtifactResolverResult result,
LiteBackendArtifactResolverStatus status,
LiteBackendArtifactResolverIssue issue,
std::string message)
{
result.status = status;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
return result;
}
LiteBackendActivationReadinessResult stoppedActivationResult(
LiteBackendActivationReadinessResult result,
LiteBackendActivationReadinessStatus status,
LiteBackendActivationReadinessIssue issue,
std::string message,
WalletBackendState backendState = WalletBackendState::Unavailable)
{
result.status = status;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
result.connectionStatus = WalletBackendStatus{backendState, result.error, {}, {}, 0.0};
return result;
}
std::string lowerCopy(const std::string& value)
{
std::string lowered;
lowered.reserve(value.size());
for (const unsigned char character : value) {
lowered.push_back(static_cast<char>(std::tolower(character)));
}
return lowered;
}
std::string trimCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
std::string baseNameLower(const std::string& path)
{
const auto slash = path.find_last_of("/\\");
const std::string name = slash == std::string::npos ? path : path.substr(slash + 1);
return lowerCopy(name);
}
bool endsWith(const std::string& value, const char* suffix)
{
const std::string suffixValue(suffix);
return value.size() >= suffixValue.size() &&
value.compare(value.size() - suffixValue.size(), suffixValue.size(), suffixValue) == 0;
}
LiteBackendArtifactPlatform normalizePlatform(LiteBackendArtifactPlatform platform)
{
return platform == LiteBackendArtifactPlatform::Current
? currentLiteBackendArtifactPlatform()
: platform;
}
LiteBackendArtifactKind inferArtifactKind(const std::string& path)
{
const std::string name = baseNameLower(path);
if (endsWith(name, ".a") || endsWith(name, ".lib")) return LiteBackendArtifactKind::StaticLibrary;
if (endsWith(name, ".so") || endsWith(name, ".dll") || endsWith(name, ".dylib")) {
return LiteBackendArtifactKind::SharedLibrary;
}
if (endsWith(name, ".exe") || name == "silentdragonxlite" || name == "silentdragonx-lite") {
return LiteBackendArtifactKind::Executable;
}
return LiteBackendArtifactKind::Unknown;
}
bool artifactKindSupported(LiteBackendArtifactKind kind)
{
return kind == LiteBackendArtifactKind::StaticLibrary ||
kind == LiteBackendArtifactKind::SharedLibrary ||
kind == LiteBackendArtifactKind::Executable;
}
std::vector<std::string> defaultArtifactNames(LiteBackendArtifactPlatform platform,
LiteBackendArtifactKind kind)
{
const auto normalizedPlatform = normalizePlatform(platform);
std::vector<std::string> names;
const auto addNamesForKind = [&](LiteBackendArtifactKind artifactKind) {
switch (normalizedPlatform) {
case LiteBackendArtifactPlatform::Windows:
if (artifactKind == LiteBackendArtifactKind::StaticLibrary) {
names.push_back("silentdragonxlite.lib");
names.push_back("libsilentdragonxlite.a");
} else if (artifactKind == LiteBackendArtifactKind::SharedLibrary) {
names.push_back("silentdragonxlite.dll");
} else if (artifactKind == LiteBackendArtifactKind::Executable) {
names.push_back("silentdragonxlite.exe");
names.push_back("silentdragonx-lite.exe");
}
break;
case LiteBackendArtifactPlatform::MacOS:
if (artifactKind == LiteBackendArtifactKind::StaticLibrary) {
names.push_back("libsilentdragonxlite.a");
names.push_back("silentdragonxlite.a");
} else if (artifactKind == LiteBackendArtifactKind::SharedLibrary) {
names.push_back("libsilentdragonxlite.dylib");
names.push_back("silentdragonxlite.dylib");
} else if (artifactKind == LiteBackendArtifactKind::Executable) {
names.push_back("silentdragonxlite");
names.push_back("silentdragonx-lite");
}
break;
case LiteBackendArtifactPlatform::Linux:
case LiteBackendArtifactPlatform::Current:
case LiteBackendArtifactPlatform::Unknown:
if (artifactKind == LiteBackendArtifactKind::StaticLibrary) {
names.push_back("silentdragonxlite.a");
names.push_back("libsilentdragonxlite.a");
} else if (artifactKind == LiteBackendArtifactKind::SharedLibrary) {
names.push_back("libsilentdragonxlite.so");
names.push_back("silentdragonxlite.so");
} else if (artifactKind == LiteBackendArtifactKind::Executable) {
names.push_back("silentdragonxlite");
names.push_back("silentdragonx-lite");
}
break;
}
};
if (kind == LiteBackendArtifactKind::Unknown) {
addNamesForKind(LiteBackendArtifactKind::StaticLibrary);
addNamesForKind(LiteBackendArtifactKind::SharedLibrary);
addNamesForKind(LiteBackendArtifactKind::Executable);
} else {
addNamesForKind(kind);
}
return names;
}
fs::path resolvePath(const std::string& projectRoot, const std::string& path)
{
const fs::path value(path);
if (value.is_absolute()) return value;
const fs::path root = projectRoot.empty() ? fs::path(".") : fs::path(projectRoot);
return root / value;
}
std::vector<LiteBackendArtifactCandidate> expandSearchRoots(
const LiteBackendArtifactResolverInput& input,
LiteBackendArtifactResolverResult& result,
LiteBackendArtifactResolverOptions options)
{
std::vector<LiteBackendArtifactCandidate> candidates;
result.searchRootCount = input.searchRoots.size();
for (const auto& root : input.searchRoots) {
if (options.requireArtifactCandidates && !root.configured) {
result = stoppedResolverResult(
std::move(result),
LiteBackendArtifactResolverStatus::WaitingForArtifactSearchRoot,
LiteBackendArtifactResolverIssue::ArtifactSearchRootNotConfigured,
"lite backend artifact search root is not configured");
return {};
}
if (trimCopy(root.rootPath).empty()) {
result = stoppedResolverResult(
std::move(result),
LiteBackendArtifactResolverStatus::WaitingForArtifactSearchRoot,
LiteBackendArtifactResolverIssue::ArtifactSearchRootMissing,
"lite backend artifact search root path is missing");
return {};
}
const fs::path rootPath = resolvePath(input.projectRoot, root.rootPath);
std::error_code error;
if (!fs::exists(rootPath, error) || error) {
result = stoppedResolverResult(
std::move(result),
LiteBackendArtifactResolverStatus::WaitingForArtifactSearchRoot,
LiteBackendArtifactResolverIssue::ArtifactSearchRootMissing,
"lite backend artifact search root does not exist");
return {};
}
if (!fs::is_directory(rootPath, error) || error) {
result = stoppedResolverResult(
std::move(result),
LiteBackendArtifactResolverStatus::WaitingForArtifactSearchRoot,
LiteBackendArtifactResolverIssue::ArtifactSearchRootNotDirectory,
"lite backend artifact search root is not a directory");
return {};
}
const auto names = root.expectedArtifactNames.empty()
? defaultArtifactNames(root.platform, root.kind)
: root.expectedArtifactNames;
for (const auto& artifactName : names) {
LiteBackendArtifactCandidate candidate;
candidate.configured = true;
candidate.artifactPath = (rootPath / artifactName).string();
candidate.platform = root.platform;
candidate.kind = root.kind;
candidate.versionLabel = root.versionLabel;
candidate.provenance = root.provenance;
candidate.signatureVerification = root.signatureVerification;
candidate.sdxlCompatible = root.sdxlCompatible;
candidate.symbols = root.symbols;
candidate.executableMetadataRequired = root.executableMetadataRequired;
candidates.push_back(std::move(candidate));
}
}
result.artifactSearchRootsAccepted = true;
return candidates;
}
bool coreSymbolsReady(const LiteWalletSdxlArtifactSymbolsInput& symbols)
{
return symbols.walletExists &&
symbols.initializeNew &&
symbols.initializeNewFromPhrase &&
symbols.initializeExisting &&
symbols.execute &&
symbols.checkServerOnline;
}
bool fileReadable(const fs::path& path)
{
std::ifstream input(path, std::ios::binary);
return input.good();
}
bool fileExecutable(const fs::path& path, LiteBackendArtifactPlatform platform)
{
const std::string name = baseNameLower(path.string());
if (normalizePlatform(platform) == LiteBackendArtifactPlatform::Windows && endsWith(name, ".exe")) return true;
std::error_code error;
const auto permissions = fs::status(path, error).permissions();
if (error) return false;
return (permissions & fs::perms::owner_exec) != fs::perms::none ||
(permissions & fs::perms::group_exec) != fs::perms::none ||
(permissions & fs::perms::others_exec) != fs::perms::none;
}
bool provenanceComplete(const LiteBackendArtifactProvenanceMetadata& provenance)
{
return provenance.metadataProvided &&
!trimCopy(provenance.source).empty() &&
!trimCopy(provenance.builder).empty() &&
!trimCopy(provenance.sourceRevision).empty() &&
!trimCopy(provenance.artifactSetId).empty();
}
CandidateInspectionResult inspectCandidate(const LiteBackendArtifactResolverInput& input,
const LiteBackendArtifactCandidate& candidate,
LiteBackendArtifactResolverOptions options)
{
CandidateInspectionResult inspection;
if (options.requireArtifactCandidates && !candidate.configured) {
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactCandidateNotConfigured;
inspection.message = "lite backend artifact candidate is not configured";
return inspection;
}
if (trimCopy(candidate.artifactPath).empty()) {
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactPathMissing;
inspection.message = "lite backend artifact candidate path is missing";
return inspection;
}
const fs::path artifactPath = resolvePath(input.projectRoot, candidate.artifactPath);
std::error_code error;
if (!fs::exists(artifactPath, error) || error) {
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactMissing;
inspection.message = "lite backend artifact candidate does not exist";
return inspection;
}
if (!fs::is_regular_file(artifactPath, error) || error) {
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactNotRegularFile;
inspection.message = "lite backend artifact candidate is not a regular file";
return inspection;
}
if (!fileReadable(artifactPath)) {
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactUnreadable;
inspection.message = "lite backend artifact candidate is not readable";
return inspection;
}
const auto candidatePlatform = normalizePlatform(candidate.platform);
const auto expectedPlatform = normalizePlatform(input.expectedPlatform);
if (candidatePlatform == LiteBackendArtifactPlatform::Unknown || candidatePlatform != expectedPlatform) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::PlatformMismatch;
inspection.message = "lite backend artifact platform metadata does not match the expected platform";
return inspection;
}
const auto kind = candidate.kind == LiteBackendArtifactKind::Unknown
? inferArtifactKind(candidate.artifactPath)
: candidate.kind;
if (!artifactKindSupported(kind)) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::UnsupportedArtifactKind;
inspection.message = "lite backend artifact kind is unsupported or unknown";
return inspection;
}
if (options.requireVersionMetadata && trimCopy(candidate.versionLabel).empty()) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::VersionMissing;
inspection.message = "lite backend artifact version metadata is missing";
return inspection;
}
if (options.requireProvenanceMetadata) {
if (!candidate.provenance.ownerReady) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ProvenanceOwnerMissing;
inspection.message = "lite backend artifact provenance owner is not ready";
return inspection;
}
if (!provenanceComplete(candidate.provenance)) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ProvenanceMetadataMissing;
inspection.message = "lite backend artifact provenance metadata is incomplete";
return inspection;
}
}
if (options.requireSdxlCompatibility && !candidate.sdxlCompatible) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactNotSdxlCompatible;
inspection.message = "lite backend artifact is not marked SDXL-compatible";
return inspection;
}
if (options.requireSdxlSymbols) {
if (!coreSymbolsReady(candidate.symbols)) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactSymbolsMissing;
inspection.message = "lite backend artifact is missing required SDXL bridge symbol metadata";
return inspection;
}
if (!candidate.symbols.freeString) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactStringOwnershipUnverified;
inspection.message = "lite backend artifact string cleanup symbol metadata is missing";
return inspection;
}
if (!candidate.symbols.shutdown) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ArtifactShutdownUnavailable;
inspection.message = "lite backend artifact shutdown symbol metadata is missing";
return inspection;
}
}
const bool executable = fileExecutable(artifactPath, candidatePlatform);
if ((kind == LiteBackendArtifactKind::Executable || candidate.executableMetadataRequired) && !executable) {
inspection.status = LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata;
inspection.issue = LiteBackendArtifactResolverIssue::ExecutableMetadataMissing;
inspection.message = "lite backend executable artifact is not marked executable";
return inspection;
}
std::size_t artifactSizeBytes = 0;
const auto fileSize = fs::file_size(artifactPath, error);
if (!error) artifactSizeBytes = static_cast<std::size_t>(fileSize);
inspection.ok = true;
inspection.artifact.found = true;
inspection.artifact.artifactPath = artifactPath.string();
inspection.artifact.platform = candidatePlatform;
inspection.artifact.kind = kind;
inspection.artifact.versionLabel = candidate.versionLabel;
inspection.artifact.provenance = candidate.provenance;
inspection.artifact.signatureVerification = candidate.signatureVerification;
inspection.artifact.exists = true;
inspection.artifact.regularFile = true;
inspection.artifact.readable = true;
inspection.artifact.executable = executable;
inspection.artifact.sdxlCompatible = candidate.sdxlCompatible;
inspection.artifact.symbols = candidate.symbols;
inspection.artifact.artifactSizeBytes = artifactSizeBytes;
inspection.syncArtifactInput.pathConfigured = true;
inspection.syncArtifactInput.exists = true;
inspection.syncArtifactInput.readable = true;
inspection.syncArtifactInput.sdxlCompatible = candidate.sdxlCompatible;
inspection.syncArtifactInput.artifactPath = artifactPath.string();
inspection.syncArtifactInput.versionLabel = candidate.versionLabel;
inspection.syncArtifactInput.symbols = candidate.symbols;
return inspection;
}
LiteBackendArtifactResolverResult rejectResolverRuntimeAction(
LiteBackendArtifactResolverResult result,
const LiteBackendArtifactResolverInput& input,
LiteBackendArtifactResolverOptions options)
{
if (options.rejectArtifactMutation && input.artifactMutationRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::ArtifactMutationRequested,
"lite backend artifact mutation is disabled for artifact resolution");
}
if (options.rejectSdxlApiCalls && input.sdxlApiRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::SdxlApiRequested,
"SDXL API calls are disabled for artifact resolution");
}
if (options.rejectServerConnectivityChecks && input.serverConnectivityCheckRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::ServerConnectivityCheckRequested,
"server connectivity checks are disabled for artifact resolution");
}
if (options.rejectWalletLifecycle && input.walletLifecycleRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::WalletLifecycleRequested,
"wallet lifecycle execution is disabled for artifact resolution");
}
if (options.rejectSync && input.syncRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::SyncRequested,
"lite sync execution is disabled for artifact resolution");
}
if (options.rejectSyncStatusPolling && input.syncStatusPollingRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::SyncStatusPollingRequested,
"lite syncstatus polling is disabled for artifact resolution");
}
if (options.rejectWorkerQueue && input.workerQueueRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::WorkerQueueRequested,
"worker queue enqueue is disabled for artifact resolution");
}
if (options.rejectWalletStateMutation && input.walletStateMutationRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::WalletStateMutationRequested,
"WalletState mutation is disabled for artifact resolution");
}
if (options.rejectWalletPersistence && input.walletPersistenceRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::WalletPersistenceRequested,
"wallet persistence is disabled for artifact resolution");
}
if (options.rejectUpload && input.uploadRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::UploadRequested,
"upload is disabled for artifact resolution");
}
if (options.rejectSigning && input.signingRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::SigningRequested,
"signing is disabled for artifact resolution");
}
if (options.rejectPublication && input.publicationRequested) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::RuntimeActionDisabled,
LiteBackendArtifactResolverIssue::PublicationRequested,
"publication is disabled for artifact resolution");
}
return result;
}
LiteBackendActivationReadinessResult rejectActivationRuntimeAction(
LiteBackendActivationReadinessResult result,
const LiteBackendActivationReadinessInput& input,
LiteBackendActivationReadinessOptions options)
{
if (options.rejectArtifactMutation && input.artifactMutationRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::ArtifactMutationRequested,
"lite backend artifact mutation is disabled for activation readiness");
}
if (options.rejectSdxlApiCalls && input.sdxlApiRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::SdxlApiRequested,
"SDXL API calls are disabled for activation readiness");
}
if (options.rejectServerConnectivityChecks && input.serverConnectivityCheckRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::ServerConnectivityCheckRequested,
"server connectivity checks are disabled for activation readiness");
}
if (options.rejectWalletLifecycle && input.walletLifecycleRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::WalletLifecycleRequested,
"wallet lifecycle execution is disabled for activation readiness");
}
if (options.rejectSync && input.syncRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::SyncRequested,
"lite sync execution is disabled for activation readiness");
}
if (options.rejectSyncStatusPolling && input.syncStatusPollingRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::SyncStatusPollingRequested,
"lite syncstatus polling is disabled for activation readiness");
}
if (options.rejectWorkerQueue && input.workerQueueRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::WorkerQueueRequested,
"worker queue enqueue is disabled for activation readiness");
}
if (options.rejectWalletStateMutation && input.walletStateMutationRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::WalletStateMutationRequested,
"WalletState mutation is disabled for activation readiness");
}
if (options.rejectWalletPersistence && input.walletPersistenceRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::WalletPersistenceRequested,
"wallet persistence is disabled for activation readiness");
}
if (options.rejectUpload && input.uploadRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::UploadRequested,
"upload is disabled for activation readiness");
}
if (options.rejectSigning && input.signingRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::SigningRequested,
"signing is disabled for activation readiness");
}
if (options.rejectPublication && input.publicationRequested) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::RuntimeActionDisabled,
LiteBackendActivationReadinessIssue::PublicationRequested,
"publication is disabled for activation readiness");
}
return result;
}
WalletBackendStatus backendStatusOrDefault(const LiteBackendBridgeReadinessInput& backend,
const std::string& fallback)
{
if (!backend.status.message.empty()) return backend.status;
return WalletBackendStatus{WalletBackendState::Unavailable, fallback, {}, {}, 0.0};
}
WalletBackendStatus readyConnectionStatus(const LiteServerSelectionResult& selectedServer)
{
const std::string serverLabel = selectedServer.server.label.empty()
? selectedServer.server.url
: selectedServer.server.label;
return WalletBackendStatus{
WalletBackendState::Disconnected,
"lite backend activation readiness accepted for " + serverLabel + "; bridge calls remain disabled here",
{},
{},
0.0
};
}
} // namespace
LiteBackendArtifactPlatform currentLiteBackendArtifactPlatform()
{
#if defined(_WIN32)
return LiteBackendArtifactPlatform::Windows;
#elif defined(__APPLE__)
return LiteBackendArtifactPlatform::MacOS;
#else
return LiteBackendArtifactPlatform::Linux;
#endif
}
const char* liteBackendArtifactPlatformName(LiteBackendArtifactPlatform platform)
{
switch (platform) {
case LiteBackendArtifactPlatform::Current: return "Current";
case LiteBackendArtifactPlatform::Linux: return "Linux";
case LiteBackendArtifactPlatform::Windows: return "Windows";
case LiteBackendArtifactPlatform::MacOS: return "MacOS";
case LiteBackendArtifactPlatform::Unknown: return "Unknown";
}
return "Unknown";
}
const char* liteBackendArtifactKindName(LiteBackendArtifactKind kind)
{
switch (kind) {
case LiteBackendArtifactKind::Unknown: return "Unknown";
case LiteBackendArtifactKind::StaticLibrary: return "StaticLibrary";
case LiteBackendArtifactKind::SharedLibrary: return "SharedLibrary";
case LiteBackendArtifactKind::Executable: return "Executable";
}
return "Unknown";
}
const char* liteBackendArtifactResolverStatusName(LiteBackendArtifactResolverStatus status)
{
switch (status) {
case LiteBackendArtifactResolverStatus::ReadyForActivationReadiness: return "ReadyForActivationReadiness";
case LiteBackendArtifactResolverStatus::WaitingForResolverOwner: return "WaitingForResolverOwner";
case LiteBackendArtifactResolverStatus::WaitingForReadOnlyGate: return "WaitingForReadOnlyGate";
case LiteBackendArtifactResolverStatus::WaitingForArtifactSearchRoot: return "WaitingForArtifactSearchRoot";
case LiteBackendArtifactResolverStatus::WaitingForArtifactCandidate: return "WaitingForArtifactCandidate";
case LiteBackendArtifactResolverStatus::WaitingForArtifactMetadata: return "WaitingForArtifactMetadata";
case LiteBackendArtifactResolverStatus::RuntimeActionDisabled: return "RuntimeActionDisabled";
}
return "Unknown";
}
const char* liteBackendArtifactResolverIssueName(LiteBackendArtifactResolverIssue issue)
{
switch (issue) {
case LiteBackendArtifactResolverIssue::ResolverOwnerMissing: return "ResolverOwnerMissing";
case LiteBackendArtifactResolverIssue::ReadOnlyGateMissing: return "ReadOnlyGateMissing";
case LiteBackendArtifactResolverIssue::ArtifactSearchRootMissing: return "ArtifactSearchRootMissing";
case LiteBackendArtifactResolverIssue::ArtifactSearchRootNotConfigured: return "ArtifactSearchRootNotConfigured";
case LiteBackendArtifactResolverIssue::ArtifactSearchRootNotDirectory: return "ArtifactSearchRootNotDirectory";
case LiteBackendArtifactResolverIssue::ArtifactCandidateMissing: return "ArtifactCandidateMissing";
case LiteBackendArtifactResolverIssue::ArtifactCandidateNotConfigured: return "ArtifactCandidateNotConfigured";
case LiteBackendArtifactResolverIssue::ArtifactPathMissing: return "ArtifactPathMissing";
case LiteBackendArtifactResolverIssue::ArtifactMissing: return "ArtifactMissing";
case LiteBackendArtifactResolverIssue::ArtifactNotRegularFile: return "ArtifactNotRegularFile";
case LiteBackendArtifactResolverIssue::ArtifactUnreadable: return "ArtifactUnreadable";
case LiteBackendArtifactResolverIssue::UnsupportedArtifactKind: return "UnsupportedArtifactKind";
case LiteBackendArtifactResolverIssue::PlatformMismatch: return "PlatformMismatch";
case LiteBackendArtifactResolverIssue::VersionMissing: return "VersionMissing";
case LiteBackendArtifactResolverIssue::ProvenanceOwnerMissing: return "ProvenanceOwnerMissing";
case LiteBackendArtifactResolverIssue::ProvenanceMetadataMissing: return "ProvenanceMetadataMissing";
case LiteBackendArtifactResolverIssue::ArtifactNotSdxlCompatible: return "ArtifactNotSdxlCompatible";
case LiteBackendArtifactResolverIssue::ArtifactSymbolsMissing: return "ArtifactSymbolsMissing";
case LiteBackendArtifactResolverIssue::ArtifactStringOwnershipUnverified: return "ArtifactStringOwnershipUnverified";
case LiteBackendArtifactResolverIssue::ArtifactShutdownUnavailable: return "ArtifactShutdownUnavailable";
case LiteBackendArtifactResolverIssue::ExecutableMetadataMissing: return "ExecutableMetadataMissing";
case LiteBackendArtifactResolverIssue::ArtifactMutationRequested: return "ArtifactMutationRequested";
case LiteBackendArtifactResolverIssue::SdxlApiRequested: return "SdxlApiRequested";
case LiteBackendArtifactResolverIssue::ServerConnectivityCheckRequested: return "ServerConnectivityCheckRequested";
case LiteBackendArtifactResolverIssue::WalletLifecycleRequested: return "WalletLifecycleRequested";
case LiteBackendArtifactResolverIssue::SyncRequested: return "SyncRequested";
case LiteBackendArtifactResolverIssue::SyncStatusPollingRequested: return "SyncStatusPollingRequested";
case LiteBackendArtifactResolverIssue::WorkerQueueRequested: return "WorkerQueueRequested";
case LiteBackendArtifactResolverIssue::WalletStateMutationRequested: return "WalletStateMutationRequested";
case LiteBackendArtifactResolverIssue::WalletPersistenceRequested: return "WalletPersistenceRequested";
case LiteBackendArtifactResolverIssue::UploadRequested: return "UploadRequested";
case LiteBackendArtifactResolverIssue::SigningRequested: return "SigningRequested";
case LiteBackendArtifactResolverIssue::PublicationRequested: return "PublicationRequested";
}
return "Unknown";
}
const char* liteBackendActivationReadinessStatusName(LiteBackendActivationReadinessStatus status)
{
switch (status) {
case LiteBackendActivationReadinessStatus::ReadyForConnectionReadiness: return "ReadyForConnectionReadiness";
case LiteBackendActivationReadinessStatus::WaitingForActivationOwner: return "WaitingForActivationOwner";
case LiteBackendActivationReadinessStatus::WaitingForReadOnlyGate: return "WaitingForReadOnlyGate";
case LiteBackendActivationReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild";
case LiteBackendActivationReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability";
case LiteBackendActivationReadinessStatus::WaitingForArtifactResolver: return "WaitingForArtifactResolver";
case LiteBackendActivationReadinessStatus::WaitingForBackendLink: return "WaitingForBackendLink";
case LiteBackendActivationReadinessStatus::WaitingForBridge: return "WaitingForBridge";
case LiteBackendActivationReadinessStatus::WaitingForConnectionSettings: return "WaitingForConnectionSettings";
case LiteBackendActivationReadinessStatus::RuntimeActionDisabled: return "RuntimeActionDisabled";
}
return "Unknown";
}
const char* liteBackendActivationReadinessIssueName(LiteBackendActivationReadinessIssue issue)
{
switch (issue) {
case LiteBackendActivationReadinessIssue::ActivationOwnerMissing: return "ActivationOwnerMissing";
case LiteBackendActivationReadinessIssue::ReadOnlyGateMissing: return "ReadOnlyGateMissing";
case LiteBackendActivationReadinessIssue::FullNodeBuild: return "FullNodeBuild";
case LiteBackendActivationReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing";
case LiteBackendActivationReadinessIssue::ArtifactResolverRejected: return "ArtifactResolverRejected";
case LiteBackendActivationReadinessIssue::BackendNotLinked: return "BackendNotLinked";
case LiteBackendActivationReadinessIssue::BridgeUnavailable: return "BridgeUnavailable";
case LiteBackendActivationReadinessIssue::ConnectionSettingsRejected: return "ConnectionSettingsRejected";
case LiteBackendActivationReadinessIssue::ArtifactMutationRequested: return "ArtifactMutationRequested";
case LiteBackendActivationReadinessIssue::SdxlApiRequested: return "SdxlApiRequested";
case LiteBackendActivationReadinessIssue::ServerConnectivityCheckRequested: return "ServerConnectivityCheckRequested";
case LiteBackendActivationReadinessIssue::WalletLifecycleRequested: return "WalletLifecycleRequested";
case LiteBackendActivationReadinessIssue::SyncRequested: return "SyncRequested";
case LiteBackendActivationReadinessIssue::SyncStatusPollingRequested: return "SyncStatusPollingRequested";
case LiteBackendActivationReadinessIssue::WorkerQueueRequested: return "WorkerQueueRequested";
case LiteBackendActivationReadinessIssue::WalletStateMutationRequested: return "WalletStateMutationRequested";
case LiteBackendActivationReadinessIssue::WalletPersistenceRequested: return "WalletPersistenceRequested";
case LiteBackendActivationReadinessIssue::UploadRequested: return "UploadRequested";
case LiteBackendActivationReadinessIssue::SigningRequested: return "SigningRequested";
case LiteBackendActivationReadinessIssue::PublicationRequested: return "PublicationRequested";
}
return "Unknown";
}
LiteBackendArtifactResolverResult evaluateLiteBackendArtifactResolver(
const LiteBackendArtifactResolverInput& input,
LiteBackendArtifactResolverOptions options)
{
LiteBackendArtifactResolverResult result;
result = rejectResolverRuntimeAction(std::move(result), input, options);
if (!result.issues.empty()) return result;
if (options.requireResolverOwner && !input.resolverOwnerReady) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::WaitingForResolverOwner,
LiteBackendArtifactResolverIssue::ResolverOwnerMissing,
"lite backend artifact resolver owner is not ready");
}
result.resolverOwnerAccepted = true;
if (options.requireReadOnlyGate && !input.readOnlyGateReady) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::WaitingForReadOnlyGate,
LiteBackendArtifactResolverIssue::ReadOnlyGateMissing,
"lite backend artifact resolver read-only gate is not ready");
}
result.readOnlyGateAccepted = true;
auto candidates = expandSearchRoots(input, result, options);
if (!result.issues.empty()) return result;
for (const auto& candidate : input.candidates) candidates.push_back(candidate);
result.candidateCount = candidates.size();
if (options.requireArtifactCandidates && candidates.empty()) {
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::WaitingForArtifactCandidate,
LiteBackendArtifactResolverIssue::ArtifactCandidateMissing,
"lite backend artifact resolver has no configured candidates");
}
result.artifactCandidatesAccepted = true;
CandidateInspectionResult firstRejected;
bool haveRejectedCandidate = false;
for (const auto& candidate : candidates) {
++result.checkedCandidateCount;
auto inspection = inspectCandidate(input, candidate, options);
if (!inspection.ok) {
++result.rejectedCandidateCount;
if (!haveRejectedCandidate) {
firstRejected = std::move(inspection);
haveRejectedCandidate = true;
}
continue;
}
result.ok = true;
result.status = LiteBackendArtifactResolverStatus::ReadyForActivationReadiness;
result.artifact = std::move(inspection.artifact);
result.syncArtifactInput = std::move(inspection.syncArtifactInput);
result.artifactDiscovered = true;
result.artifactMetadataAccepted = true;
result.platformAccepted = true;
result.versionAccepted = !options.requireVersionMetadata || !result.artifact.versionLabel.empty();
result.provenanceAccepted = !options.requireProvenanceMetadata || provenanceComplete(result.artifact.provenance);
result.sdxlCompatibilityAccepted = !options.requireSdxlCompatibility || result.artifact.sdxlCompatible;
result.symbolMetadataAccepted = !options.requireSdxlSymbols ||
(coreSymbolsReady(result.artifact.symbols) && result.artifact.symbols.freeString && result.artifact.symbols.shutdown);
result.executableMetadataAccepted = true;
result.syncArtifactInputProduced = true;
return result;
}
if (haveRejectedCandidate) {
return stoppedResolverResult(std::move(result), firstRejected.status, firstRejected.issue, firstRejected.message);
}
return stoppedResolverResult(std::move(result), LiteBackendArtifactResolverStatus::WaitingForArtifactCandidate,
LiteBackendArtifactResolverIssue::ArtifactCandidateMissing,
"lite backend artifact resolver did not find any artifact candidates");
}
LiteBackendActivationReadinessResult evaluateLiteBackendActivationReadiness(
const LiteBackendActivationReadinessInput& input,
LiteBackendActivationReadinessOptions options)
{
LiteBackendActivationReadinessResult result;
result.connectionAvailability = LiteConnectionAvailability::BackendUnavailable;
result = rejectActivationRuntimeAction(std::move(result), input, options);
if (!result.issues.empty()) return result;
if (options.requireActivationOwner && !input.activationOwnerReady) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForActivationOwner,
LiteBackendActivationReadinessIssue::ActivationOwnerMissing,
"lite backend activation readiness owner is not ready");
}
result.activationOwnerAccepted = true;
if (options.requireReadOnlyGate && !input.readOnlyGateReady) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForReadOnlyGate,
LiteBackendActivationReadinessIssue::ReadOnlyGateMissing,
"lite backend activation readiness read-only gate is not ready");
}
result.readOnlyGateAccepted = true;
if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) {
result.connectionAvailability = LiteConnectionAvailability::UnsupportedBuild;
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForLiteBuild,
LiteBackendActivationReadinessIssue::FullNodeBuild,
"lite backend activation readiness requires a lite build");
}
result.liteBuildAccepted = true;
if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) {
result.connectionAvailability = LiteConnectionAvailability::BackendUnavailable;
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForBackendCapability,
LiteBackendActivationReadinessIssue::LiteBackendCapabilityMissing,
"lite backend capability is not available for activation readiness");
}
result.backendCapabilityAccepted = true;
result.artifactResolverResult = evaluateLiteBackendArtifactResolver(input.artifactResolver, options.resolverOptions);
if (options.requireResolvedArtifact && !result.artifactResolverResult.ok) {
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForArtifactResolver,
LiteBackendActivationReadinessIssue::ArtifactResolverRejected,
result.artifactResolverResult.error.empty()
? "lite backend artifact resolver did not produce a usable artifact"
: result.artifactResolverResult.error);
}
result.artifactResolverAccepted = true;
result.syncArtifactInput = result.artifactResolverResult.syncArtifactInput;
if (options.requireLinkedBackend && !input.backend.linked) {
result.connectionAvailability = LiteConnectionAvailability::BackendUnavailable;
const auto status = backendStatusOrDefault(input.backend, "lite backend is not linked");
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForBackendLink,
LiteBackendActivationReadinessIssue::BackendNotLinked,
status.message);
}
result.backendLinkedAccepted = true;
if (options.requireBridgeAvailable && !input.backend.bridgeAvailable) {
result.connectionAvailability = LiteConnectionAvailability::BridgeUnavailable;
const std::string message = !input.backend.bridgeUnavailableReason.empty()
? input.backend.bridgeUnavailableReason
: backendStatusOrDefault(input.backend, "lite client bridge is unavailable").message;
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForBridge,
LiteBackendActivationReadinessIssue::BridgeUnavailable,
message);
}
result.bridgeAccepted = true;
result.selectedServer = selectLiteServer(input.connectionSettings);
if (options.requireUsableServer && !result.selectedServer.ok) {
result.connectionAvailability = LiteConnectionAvailability::NoUsableServer;
return stoppedActivationResult(std::move(result), LiteBackendActivationReadinessStatus::WaitingForConnectionSettings,
LiteBackendActivationReadinessIssue::ConnectionSettingsRejected,
result.selectedServer.error.empty()
? "no usable lite server is configured for activation readiness"
: result.selectedServer.error,
WalletBackendState::Error);
}
result.connectionSettingsAccepted = true;
result.syncBackendInput.linked = input.backend.linked;
result.syncBackendInput.bridgeAvailable = input.backend.bridgeAvailable;
result.syncBackendInput.status = input.backend.status.message.empty()
? WalletBackendStatus{WalletBackendState::Disconnected, "lite backend bridge readiness accepted", {}, {}, 0.0}
: input.backend.status;
result.syncBackendInput.bridgeUnavailableReason = input.backend.bridgeUnavailableReason;
result.syncReadinessInputsProduced = true;
result.ok = true;
result.status = LiteBackendActivationReadinessStatus::ReadyForConnectionReadiness;
result.connectionAvailability = LiteConnectionAvailability::Ready;
result.connectionStatus = readyConnectionStatus(result.selectedServer);
result.connectionServiceBoundaryAccepted = true;
return result;
}
LiteBackendArtifactResolver::LiteBackendArtifactResolver(LiteBackendArtifactResolverOptions options)
: options_(options)
{
}
LiteBackendArtifactResolverResult LiteBackendArtifactResolver::resolve(
const LiteBackendArtifactResolverInput& input) const
{
return evaluateLiteBackendArtifactResolver(input, options_);
}
LiteBackendActivationReadinessAdapter::LiteBackendActivationReadinessAdapter(
LiteBackendActivationReadinessOptions options)
: options_(options)
{
}
LiteBackendActivationReadinessResult LiteBackendActivationReadinessAdapter::evaluate(
const LiteBackendActivationReadinessInput& input) const
{
return evaluateLiteBackendActivationReadiness(input, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,414 @@
#pragma once
#include "lite_connection_service.h"
#include "lite_wallet_sync_execution_readiness.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteBackendArtifactPlatform {
Current,
Linux,
Windows,
MacOS,
Unknown,
};
enum class LiteBackendArtifactKind {
Unknown,
StaticLibrary,
SharedLibrary,
Executable,
};
enum class LiteBackendArtifactResolverStatus {
ReadyForActivationReadiness,
WaitingForResolverOwner,
WaitingForReadOnlyGate,
WaitingForArtifactSearchRoot,
WaitingForArtifactCandidate,
WaitingForArtifactMetadata,
RuntimeActionDisabled,
};
enum class LiteBackendArtifactResolverIssue {
ResolverOwnerMissing,
ReadOnlyGateMissing,
ArtifactSearchRootMissing,
ArtifactSearchRootNotConfigured,
ArtifactSearchRootNotDirectory,
ArtifactCandidateMissing,
ArtifactCandidateNotConfigured,
ArtifactPathMissing,
ArtifactMissing,
ArtifactNotRegularFile,
ArtifactUnreadable,
UnsupportedArtifactKind,
PlatformMismatch,
VersionMissing,
ProvenanceOwnerMissing,
ProvenanceMetadataMissing,
ArtifactNotSdxlCompatible,
ArtifactSymbolsMissing,
ArtifactStringOwnershipUnverified,
ArtifactShutdownUnavailable,
ExecutableMetadataMissing,
ArtifactMutationRequested,
SdxlApiRequested,
ServerConnectivityCheckRequested,
WalletLifecycleRequested,
SyncRequested,
SyncStatusPollingRequested,
WorkerQueueRequested,
WalletStateMutationRequested,
WalletPersistenceRequested,
UploadRequested,
SigningRequested,
PublicationRequested,
};
struct LiteBackendArtifactProvenanceMetadata {
bool ownerReady = false;
bool metadataProvided = false;
std::string source;
std::string builder;
std::string sourceRevision;
std::string artifactSetId;
bool redacted = true;
};
struct LiteBackendArtifactSignatureVerificationMetadata {
bool policyDefined = false;
bool requiredForRelease = false;
bool metadataProvided = false;
bool verificationPerformed = false;
bool verified = false;
std::string signatureFormat;
std::string signaturePath;
std::string signatureFileSha256;
std::string verificationTool;
std::string verificationCommand;
std::string keyFingerprint;
std::string certificateIdentity;
std::string certificateIssuer;
std::string transparencyLogUrl;
std::string verifiedArtifactSha256;
};
struct LiteBackendArtifactCandidate {
bool configured = false;
std::string artifactPath;
LiteBackendArtifactPlatform platform = LiteBackendArtifactPlatform::Current;
LiteBackendArtifactKind kind = LiteBackendArtifactKind::Unknown;
std::string versionLabel;
LiteBackendArtifactProvenanceMetadata provenance;
LiteBackendArtifactSignatureVerificationMetadata signatureVerification;
bool sdxlCompatible = false;
LiteWalletSdxlArtifactSymbolsInput symbols;
bool executableMetadataRequired = false;
};
struct LiteBackendArtifactSearchRoot {
bool configured = false;
std::string rootPath;
LiteBackendArtifactPlatform platform = LiteBackendArtifactPlatform::Current;
LiteBackendArtifactKind kind = LiteBackendArtifactKind::Unknown;
std::string versionLabel;
LiteBackendArtifactProvenanceMetadata provenance;
LiteBackendArtifactSignatureVerificationMetadata signatureVerification;
bool sdxlCompatible = false;
LiteWalletSdxlArtifactSymbolsInput symbols;
bool executableMetadataRequired = false;
std::vector<std::string> expectedArtifactNames;
};
struct LiteBackendArtifactResolverInput {
bool resolverOwnerReady = false;
bool readOnlyGateReady = false;
std::string projectRoot;
LiteBackendArtifactPlatform expectedPlatform = LiteBackendArtifactPlatform::Current;
std::vector<LiteBackendArtifactSearchRoot> searchRoots;
std::vector<LiteBackendArtifactCandidate> candidates;
bool artifactMutationRequested = false;
bool sdxlApiRequested = false;
bool serverConnectivityCheckRequested = false;
bool walletLifecycleRequested = false;
bool syncRequested = false;
bool syncStatusPollingRequested = false;
bool workerQueueRequested = false;
bool walletStateMutationRequested = false;
bool walletPersistenceRequested = false;
bool uploadRequested = false;
bool signingRequested = false;
bool publicationRequested = false;
};
struct LiteBackendArtifactResolverOptions {
bool requireResolverOwner = true;
bool requireReadOnlyGate = true;
bool requireArtifactCandidates = true;
bool requireVersionMetadata = true;
bool requireProvenanceMetadata = true;
bool requireSdxlCompatibility = true;
bool requireSdxlSymbols = true;
bool rejectArtifactMutation = true;
bool rejectSdxlApiCalls = true;
bool rejectServerConnectivityChecks = true;
bool rejectWalletLifecycle = true;
bool rejectSync = true;
bool rejectSyncStatusPolling = true;
bool rejectWorkerQueue = true;
bool rejectWalletStateMutation = true;
bool rejectWalletPersistence = true;
bool rejectUpload = true;
bool rejectSigning = true;
bool rejectPublication = true;
};
struct LiteBackendResolvedArtifact {
bool found = false;
std::string artifactPath;
LiteBackendArtifactPlatform platform = LiteBackendArtifactPlatform::Unknown;
LiteBackendArtifactKind kind = LiteBackendArtifactKind::Unknown;
std::string versionLabel;
LiteBackendArtifactProvenanceMetadata provenance;
LiteBackendArtifactSignatureVerificationMetadata signatureVerification;
bool exists = false;
bool regularFile = false;
bool readable = false;
bool executable = false;
bool sdxlCompatible = false;
LiteWalletSdxlArtifactSymbolsInput symbols;
std::size_t artifactSizeBytes = 0;
};
struct LiteBackendArtifactResolverIssueInfo {
LiteBackendArtifactResolverIssue issue = LiteBackendArtifactResolverIssue::ArtifactCandidateMissing;
std::string message;
};
struct LiteBackendArtifactResolverResult {
bool ok = false;
bool readOnlyArtifactDiscovery = true;
bool artifactMetadataReadOnly = true;
bool provenanceReadOnly = true;
bool executableMetadataReadOnly = true;
bool noArtifactMutation = true;
bool noSdxlCalls = true;
bool noServerConnectivityChecked = true;
bool noWalletCreated = true;
bool noWalletOpened = true;
bool noWalletRestored = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWorkerQueueEnqueue = true;
bool noWalletStateMutation = true;
bool noWalletPersistence = true;
bool noUpload = true;
bool noSigning = true;
bool noPublication = true;
bool resolverOwnerAccepted = false;
bool readOnlyGateAccepted = false;
bool artifactSearchRootsAccepted = false;
bool artifactCandidatesAccepted = false;
bool artifactDiscovered = false;
bool artifactMetadataAccepted = false;
bool platformAccepted = false;
bool versionAccepted = false;
bool provenanceAccepted = false;
bool sdxlCompatibilityAccepted = false;
bool symbolMetadataAccepted = false;
bool executableMetadataAccepted = false;
bool syncArtifactInputProduced = false;
std::size_t searchRootCount = 0;
std::size_t candidateCount = 0;
std::size_t checkedCandidateCount = 0;
std::size_t rejectedCandidateCount = 0;
LiteBackendArtifactResolverStatus status = LiteBackendArtifactResolverStatus::WaitingForResolverOwner;
LiteBackendResolvedArtifact artifact;
LiteWalletSdxlArtifactInput syncArtifactInput;
std::vector<LiteBackendArtifactResolverIssueInfo> issues;
std::string error;
};
enum class LiteBackendActivationReadinessStatus {
ReadyForConnectionReadiness,
WaitingForActivationOwner,
WaitingForReadOnlyGate,
WaitingForLiteBuild,
WaitingForBackendCapability,
WaitingForArtifactResolver,
WaitingForBackendLink,
WaitingForBridge,
WaitingForConnectionSettings,
RuntimeActionDisabled,
};
enum class LiteBackendActivationReadinessIssue {
ActivationOwnerMissing,
ReadOnlyGateMissing,
FullNodeBuild,
LiteBackendCapabilityMissing,
ArtifactResolverRejected,
BackendNotLinked,
BridgeUnavailable,
ConnectionSettingsRejected,
ArtifactMutationRequested,
SdxlApiRequested,
ServerConnectivityCheckRequested,
WalletLifecycleRequested,
SyncRequested,
SyncStatusPollingRequested,
WorkerQueueRequested,
WalletStateMutationRequested,
WalletPersistenceRequested,
UploadRequested,
SigningRequested,
PublicationRequested,
};
struct LiteBackendBridgeReadinessInput {
bool linked = false;
bool bridgeAvailable = false;
WalletBackendStatus status;
std::string bridgeUnavailableReason;
};
struct LiteBackendActivationReadinessInput {
WalletCapabilities capabilities;
LiteConnectionSettings connectionSettings;
LiteBackendArtifactResolverInput artifactResolver;
LiteBackendBridgeReadinessInput backend;
bool activationOwnerReady = false;
bool readOnlyGateReady = false;
bool artifactMutationRequested = false;
bool sdxlApiRequested = false;
bool serverConnectivityCheckRequested = false;
bool walletLifecycleRequested = false;
bool syncRequested = false;
bool syncStatusPollingRequested = false;
bool workerQueueRequested = false;
bool walletStateMutationRequested = false;
bool walletPersistenceRequested = false;
bool uploadRequested = false;
bool signingRequested = false;
bool publicationRequested = false;
};
struct LiteBackendActivationReadinessOptions {
bool requireActivationOwner = true;
bool requireReadOnlyGate = true;
bool requireLiteBuild = true;
bool requireLiteBackendCapability = true;
bool requireResolvedArtifact = true;
bool requireLinkedBackend = true;
bool requireBridgeAvailable = true;
bool requireUsableServer = true;
bool rejectArtifactMutation = true;
bool rejectSdxlApiCalls = true;
bool rejectServerConnectivityChecks = true;
bool rejectWalletLifecycle = true;
bool rejectSync = true;
bool rejectSyncStatusPolling = true;
bool rejectWorkerQueue = true;
bool rejectWalletStateMutation = true;
bool rejectWalletPersistence = true;
bool rejectUpload = true;
bool rejectSigning = true;
bool rejectPublication = true;
LiteBackendArtifactResolverOptions resolverOptions;
};
struct LiteBackendActivationReadinessIssueInfo {
LiteBackendActivationReadinessIssue issue = LiteBackendActivationReadinessIssue::ActivationOwnerMissing;
std::string message;
};
struct LiteBackendActivationReadinessResult {
bool ok = false;
bool readOnlyActivation = true;
bool artifactDiscoveryReadOnly = true;
bool connectionReadinessOnly = true;
bool noBridgeCalls = true;
bool noArtifactMutation = true;
bool noSdxlCalls = true;
bool noServerConnectivityChecked = true;
bool noWalletCreated = true;
bool noWalletOpened = true;
bool noWalletRestored = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWorkerQueueEnqueue = true;
bool noWalletStateMutation = true;
bool noWalletPersistence = true;
bool noUpload = true;
bool noSigning = true;
bool noPublication = true;
bool activationOwnerAccepted = false;
bool readOnlyGateAccepted = false;
bool liteBuildAccepted = false;
bool backendCapabilityAccepted = false;
bool artifactResolverAccepted = false;
bool backendLinkedAccepted = false;
bool bridgeAccepted = false;
bool connectionSettingsAccepted = false;
bool connectionServiceBoundaryAccepted = false;
bool syncReadinessInputsProduced = false;
LiteBackendActivationReadinessStatus status = LiteBackendActivationReadinessStatus::WaitingForActivationOwner;
LiteBackendArtifactResolverResult artifactResolverResult;
LiteWalletSdxlArtifactInput syncArtifactInput;
LiteWalletLinkedBackendReadinessInput syncBackendInput;
LiteConnectionAvailability connectionAvailability = LiteConnectionAvailability::BackendUnavailable;
LiteServerSelectionResult selectedServer;
WalletBackendStatus connectionStatus;
std::vector<LiteBackendActivationReadinessIssueInfo> issues;
std::string error;
};
LiteBackendArtifactPlatform currentLiteBackendArtifactPlatform();
const char* liteBackendArtifactPlatformName(LiteBackendArtifactPlatform platform);
const char* liteBackendArtifactKindName(LiteBackendArtifactKind kind);
const char* liteBackendArtifactResolverStatusName(LiteBackendArtifactResolverStatus status);
const char* liteBackendArtifactResolverIssueName(LiteBackendArtifactResolverIssue issue);
const char* liteBackendActivationReadinessStatusName(LiteBackendActivationReadinessStatus status);
const char* liteBackendActivationReadinessIssueName(LiteBackendActivationReadinessIssue issue);
LiteBackendArtifactResolverResult evaluateLiteBackendArtifactResolver(
const LiteBackendArtifactResolverInput& input,
LiteBackendArtifactResolverOptions options = {});
LiteBackendActivationReadinessResult evaluateLiteBackendActivationReadiness(
const LiteBackendActivationReadinessInput& input,
LiteBackendActivationReadinessOptions options = {});
class LiteBackendArtifactResolver {
public:
explicit LiteBackendArtifactResolver(LiteBackendArtifactResolverOptions options = {});
LiteBackendArtifactResolverResult resolve(const LiteBackendArtifactResolverInput& input) const;
private:
LiteBackendArtifactResolverOptions options_;
};
class LiteBackendActivationReadinessAdapter {
public:
explicit LiteBackendActivationReadinessAdapter(LiteBackendActivationReadinessOptions options = {});
LiteBackendActivationReadinessResult evaluate(const LiteBackendActivationReadinessInput& input) const;
private:
LiteBackendActivationReadinessOptions options_;
};
} // namespace dragonx::wallet

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "lite_client_bridge.h"
#include "lite_bridge_runtime.h"
#ifndef DRAGONX_ENABLE_LITE_BACKEND
#define DRAGONX_ENABLE_LITE_BACKEND 0
#endif
#if DRAGONX_ENABLE_LITE_BACKEND
extern "C" {
bool litelib_wallet_exists(const char* chain_name);
char* litelib_initialize_new(bool dangerous, const char* server);
char* litelib_initialize_new_from_phrase(bool dangerous,
const char* server,
const char* seed,
unsigned long long birthday,
unsigned long long number,
bool overwrite);
char* litelib_initialize_existing(bool dangerous, const char* server);
char* litelib_execute(const char* command, const char* args);
void litelib_rust_free_string(char* value);
bool litelib_check_server_online(const char* server);
void litelib_shutdown();
}
#endif
#include <utility>
namespace dragonx {
namespace wallet {
namespace {
bool hasRequiredApi(const LiteClientBridgeApi& api)
{
return api.walletExists &&
api.initializeNew &&
api.initializeNewFromPhrase &&
api.initializeExisting &&
api.execute &&
api.freeString &&
api.checkServerOnline &&
api.shutdown;
}
#if DRAGONX_ENABLE_LITE_BACKEND
LiteClientBridgeApi linkedSdxlApi()
{
return LiteClientBridgeApi{
&litelib_wallet_exists,
&litelib_initialize_new,
&litelib_initialize_new_from_phrase,
&litelib_initialize_existing,
&litelib_execute,
&litelib_rust_free_string,
&litelib_check_server_online,
&litelib_shutdown
};
}
#endif
} // namespace
LiteClientBridge::LiteClientBridge(LiteClientBridgeApi api, std::string unavailableReason)
: api_(api), unavailableReason_(std::move(unavailableReason))
{
}
LiteClientBridge LiteClientBridge::unavailable(std::string reason)
{
return LiteClientBridge({}, std::move(reason));
}
LiteClientBridge LiteClientBridge::fromApi(LiteClientBridgeApi api)
{
if (!hasRequiredApi(api)) {
return unavailable("lite client bridge API is incomplete");
}
return LiteClientBridge(api, {});
}
LiteClientBridge LiteClientBridge::linkedSdxl()
{
#if DRAGONX_ENABLE_LITE_BACKEND
return fromApi(linkedSdxlApi());
#else
return unavailable("lite backend is not linked");
#endif
}
LiteClientBridge::LiteClientBridge(LiteClientBridge&& other) noexcept
: api_(other.api_),
unavailableReason_(std::move(other.unavailableReason_)),
shutdownCalled_(other.shutdownCalled_)
{
other.api_ = {};
other.shutdownCalled_ = true;
}
LiteClientBridge& LiteClientBridge::operator=(LiteClientBridge&& other) noexcept
{
if (this == &other) return *this;
shutdown();
api_ = other.api_;
unavailableReason_ = std::move(other.unavailableReason_);
shutdownCalled_ = other.shutdownCalled_;
other.api_ = {};
other.shutdownCalled_ = true;
return *this;
}
LiteClientBridge::~LiteClientBridge()
{
shutdown();
}
bool LiteClientBridge::available() const
{
return hasRequiredApi(api_);
}
bool LiteClientBridge::walletExists(const std::string& chainName) const
{
if (!available()) return false;
return api_.walletExists(chainName.c_str());
}
bool LiteClientBridge::checkServerOnline(const std::string& server) const
{
if (!available()) return false;
return api_.checkServerOnline(server.c_str());
}
LiteBridgeStringResult LiteClientBridge::initializeNew(bool dangerous, const std::string& server)
{
if (!available()) return unavailableResult();
return takeOwnedString(api_.initializeNew(dangerous, server.c_str()));
}
LiteBridgeStringResult LiteClientBridge::initializeNewFromPhrase(bool dangerous,
const std::string& server,
const std::string& seed,
unsigned long long birthday,
unsigned long long account,
bool overwrite)
{
if (!available()) return unavailableResult();
return takeOwnedString(api_.initializeNewFromPhrase(
dangerous, server.c_str(), seed.c_str(), birthday, account, overwrite));
}
LiteBridgeStringResult LiteClientBridge::initializeExisting(bool dangerous, const std::string& server)
{
if (!available()) return unavailableResult();
return takeOwnedString(api_.initializeExisting(dangerous, server.c_str()));
}
LiteBridgeStringResult LiteClientBridge::execute(const std::string& command, const std::string& args)
{
if (!available()) return unavailableResult();
if (command.empty()) return {false, {}, "lite command is empty"};
return takeOwnedString(api_.execute(command.c_str(), args.c_str()));
}
void LiteClientBridge::shutdown()
{
if (shutdownCalled_) return;
shutdownCalled_ = true;
if (available()) api_.shutdown();
}
LiteBridgeStringResult LiteClientBridge::unavailableResult() const
{
return {false, {}, unavailableReason_.empty() ? "lite backend is unavailable" : unavailableReason_};
}
LiteBridgeStringResult LiteClientBridge::takeOwnedString(char* rawValue) const
{
return liteBridgeRuntimeTakeOwnedString(rawValue, api_.freeString);
}
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,84 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
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();
private:
LiteClientBridge(LiteClientBridgeApi api, std::string unavailableReason);
LiteBridgeStringResult unavailableResult() const;
LiteBridgeStringResult takeOwnedString(char* rawValue) const;
LiteClientBridgeApi api_;
std::string unavailableReason_;
bool shutdownCalled_ = false;
};
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,309 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "lite_connection_service.h"
#include <algorithm>
#include <cctype>
#include <utility>
namespace dragonx {
namespace wallet {
namespace {
std::string trimCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
bool startsWith(const std::string& value, const char* prefix)
{
const std::string prefixValue(prefix);
return value.size() >= prefixValue.size() &&
value.compare(0, prefixValue.size(), prefixValue) == 0;
}
LiteServerEndpoint trimmedEndpoint(const LiteServerEndpoint& endpoint)
{
LiteServerEndpoint normalized = endpoint;
normalized.url = trimCopy(normalized.url);
return normalized;
}
std::vector<std::pair<std::size_t, LiteServerEndpoint>> usableConfiguredServers(
const LiteConnectionSettings& settings)
{
std::vector<std::pair<std::size_t, LiteServerEndpoint>> servers;
for (std::size_t index = 0; index < settings.servers.size(); ++index) {
auto endpoint = trimmedEndpoint(settings.servers[index]);
if (!endpoint.enabled || !isLiteServerUrlUsable(endpoint.url)) continue;
servers.push_back({index, std::move(endpoint)});
}
return servers;
}
std::string normalizedChainName(const std::string& chainName)
{
const std::string normalized = trimCopy(chainName);
return normalized.empty() ? kDragonXLiteChainName : normalized;
}
} // namespace
std::vector<LiteServerEndpoint> defaultDragonXLiteServers()
{
return {
{"https://lite.dragonx.is", "DragonX Lite", true},
{"https://lite1.dragonx.is", "DragonX Lite 1", true},
{"https://lite2.dragonx.is", "DragonX Lite 2", true},
{"https://lite3.dragonx.is", "DragonX Lite 3", true},
{"https://lite4.dragonx.is", "DragonX Lite 4", true},
{"https://lite5.dragonx.is", "DragonX Lite 5", true}
};
}
LiteConnectionSettings defaultLiteConnectionSettings()
{
LiteConnectionSettings settings;
settings.servers = defaultDragonXLiteServers();
settings.selectionMode = LiteServerSelectionMode::Sticky;
settings.stickyServerUrl = kDefaultDragonXLiteServer;
settings.chainName = kDragonXLiteChainName;
settings.randomSelectionSeed = 0;
return settings;
}
bool isLiteServerUrlUsable(const std::string& serverUrl)
{
const std::string normalized = trimCopy(serverUrl);
return startsWith(normalized, "https://") || startsWith(normalized, "http://");
}
const char* liteServerSelectionModeName(LiteServerSelectionMode mode)
{
switch (mode) {
case LiteServerSelectionMode::Sticky:
return "Sticky";
case LiteServerSelectionMode::Random:
return "Random";
}
return "Unknown";
}
const char* liteConnectionAvailabilityName(LiteConnectionAvailability availability)
{
switch (availability) {
case LiteConnectionAvailability::Ready:
return "Ready";
case LiteConnectionAvailability::UnsupportedBuild:
return "UnsupportedBuild";
case LiteConnectionAvailability::BackendUnavailable:
return "BackendUnavailable";
case LiteConnectionAvailability::BridgeUnavailable:
return "BridgeUnavailable";
case LiteConnectionAvailability::NoUsableServer:
return "NoUsableServer";
}
return "Unknown";
}
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings)
{
auto usableServers = usableConfiguredServers(settings);
const std::string stickyServerUrl = trimCopy(settings.stickyServerUrl);
if (settings.selectionMode == LiteServerSelectionMode::Sticky &&
isLiteServerUrlUsable(stickyServerUrl)) {
auto match = std::find_if(usableServers.begin(), usableServers.end(), [&](const auto& candidate) {
return candidate.second.url == stickyServerUrl;
});
if (match != usableServers.end()) {
return LiteServerSelectionResult{true, match->second, match->first, false, {}};
}
return LiteServerSelectionResult{
true,
LiteServerEndpoint{stickyServerUrl, "Custom", true},
0,
true,
{}
};
}
if (usableServers.empty()) {
return LiteServerSelectionResult{false, {}, 0, false, "no usable lite servers are configured"};
}
if (settings.selectionMode == LiteServerSelectionMode::Random) {
const std::size_t selectedIndex = settings.randomSelectionSeed % usableServers.size();
return LiteServerSelectionResult{
true,
usableServers[selectedIndex].second,
usableServers[selectedIndex].first,
false,
{}
};
}
return LiteServerSelectionResult{true, usableServers.front().second, usableServers.front().first, false, {}};
}
LiteConnectionService::LiteConnectionService(WalletCapabilities capabilities,
LiteConnectionSettings settings,
LiteClientBridge bridge)
: capabilities_(capabilities), settings_(std::move(settings)), bridge_(std::move(bridge))
{
}
LiteConnectionAvailability LiteConnectionService::availability() const
{
if (!isLiteBuild(capabilities_)) return LiteConnectionAvailability::UnsupportedBuild;
if (!supportsLiteBackend(capabilities_)) return LiteConnectionAvailability::BackendUnavailable;
if (!bridge_.available()) return LiteConnectionAvailability::BridgeUnavailable;
if (!selectedServer().ok) return LiteConnectionAvailability::NoUsableServer;
return LiteConnectionAvailability::Ready;
}
WalletBackendStatus LiteConnectionService::status() const
{
const auto currentAvailability = availability();
if (currentAvailability == LiteConnectionAvailability::NoUsableServer) {
return statusFor(currentAvailability, selectedServer().error);
}
return statusFor(currentAvailability);
}
LiteServerSelectionResult LiteConnectionService::selectedServer() const
{
return selectLiteServer(settings_);
}
LiteWalletExistsRequest LiteConnectionService::walletExistsRequest() const
{
return LiteWalletExistsRequest{normalizedChainName(settings_.chainName)};
}
LiteServerHealthRequest LiteConnectionService::serverHealthRequest() const
{
auto selection = selectedServer();
if (!selection.ok) return {};
return LiteServerHealthRequest{selection.server, selection.serverIndex, selection.customServer};
}
LiteWalletExistsCheckResult LiteConnectionService::checkWalletExists()
{
LiteWalletExistsCheckResult result;
result.request = walletExistsRequest();
const auto readiness = bridgeReadinessStatus();
if (readiness.state != WalletBackendState::Disconnected) {
result.status = readiness;
result.error = readiness.message;
return result;
}
if (result.request.chainName.empty()) {
result.status = statusFor(LiteConnectionAvailability::BackendUnavailable, "lite chain name is empty");
result.error = result.status.message;
return result;
}
result.attempted = true;
result.walletExists = bridge_.walletExists(result.request.chainName);
result.ok = true;
result.status = readiness;
return result;
}
LiteServerHealthCheckResult LiteConnectionService::checkSelectedServerHealth()
{
LiteServerHealthCheckResult result;
result.request = serverHealthRequest();
const auto readiness = bridgeReadinessStatus();
if (readiness.state != WalletBackendState::Disconnected) {
result.status = readiness;
result.error = readiness.message;
return result;
}
auto selection = selectedServer();
if (!selection.ok) {
result.status = statusFor(LiteConnectionAvailability::NoUsableServer, selection.error);
result.error = result.status.message;
return result;
}
result.request = LiteServerHealthRequest{selection.server, selection.serverIndex, selection.customServer};
result.attempted = true;
result.serverOnline = bridge_.checkServerOnline(result.request.server.url);
result.ok = true;
result.status = readiness;
return result;
}
WalletBackendStatus LiteConnectionService::statusFor(LiteConnectionAvailability availability,
const std::string& detail) const
{
switch (availability) {
case LiteConnectionAvailability::Ready:
return WalletBackendStatus{
WalletBackendState::Disconnected,
detail.empty() ? "lite connection scaffold ready; wallet lifecycle is not implemented" : detail,
{},
{},
0.0
};
case LiteConnectionAvailability::UnsupportedBuild:
return WalletBackendStatus{
WalletBackendState::Unavailable,
"lite connection service is unsupported in full-node builds",
{},
{},
0.0
};
case LiteConnectionAvailability::BackendUnavailable:
return WalletBackendStatus{
WalletBackendState::Unavailable,
detail.empty() ? "lite backend is not linked" : detail,
{},
{},
0.0
};
case LiteConnectionAvailability::BridgeUnavailable:
return WalletBackendStatus{
WalletBackendState::Unavailable,
detail.empty() ? bridge_.unavailableReason() : detail,
{},
{},
0.0
};
case LiteConnectionAvailability::NoUsableServer:
return WalletBackendStatus{
WalletBackendState::Error,
detail.empty() ? "no usable lite servers are configured" : detail,
{},
{},
0.0
};
}
return WalletBackendStatus{WalletBackendState::Unavailable, "unknown lite connection state", {}, {}, 0.0};
}
WalletBackendStatus LiteConnectionService::bridgeReadinessStatus() const
{
if (!isLiteBuild(capabilities_)) return statusFor(LiteConnectionAvailability::UnsupportedBuild);
if (!supportsLiteBackend(capabilities_)) return statusFor(LiteConnectionAvailability::BackendUnavailable);
if (!bridge_.available()) return statusFor(LiteConnectionAvailability::BridgeUnavailable);
return statusFor(LiteConnectionAvailability::Ready);
}
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,123 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "lite_client_bridge.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx {
namespace wallet {
// The SDXL litelib backend identifies the production chain as "main" (its mainnet
// module carries DragonX's coin params). It hard-panics on any other chain name, so
// this MUST be one of {"main","test","regtest"} — not the coin ticker.
constexpr const char* kDragonXLiteChainName = "main";
constexpr const char* kDefaultDragonXLiteServer = "https://lite.dragonx.is";
enum class LiteServerSelectionMode {
Sticky,
Random
};
enum class LiteConnectionAvailability {
Ready,
UnsupportedBuild,
BackendUnavailable,
BridgeUnavailable,
NoUsableServer
};
struct LiteServerEndpoint {
std::string url;
std::string label;
bool enabled = true;
};
struct LiteConnectionSettings {
std::vector<LiteServerEndpoint> servers;
LiteServerSelectionMode selectionMode = LiteServerSelectionMode::Sticky;
std::string stickyServerUrl = kDefaultDragonXLiteServer;
std::string chainName = kDragonXLiteChainName;
std::size_t randomSelectionSeed = 0;
};
struct LiteServerSelectionResult {
bool ok = false;
LiteServerEndpoint server;
std::size_t serverIndex = 0;
bool customServer = false;
std::string error;
};
struct LiteWalletExistsRequest {
std::string chainName = kDragonXLiteChainName;
};
struct LiteServerHealthRequest {
LiteServerEndpoint server;
std::size_t serverIndex = 0;
bool customServer = false;
};
struct LiteWalletExistsCheckResult {
bool ok = false;
bool attempted = false;
bool walletExists = false;
LiteWalletExistsRequest request;
WalletBackendStatus status;
std::string error;
};
struct LiteServerHealthCheckResult {
bool ok = false;
bool attempted = false;
bool serverOnline = false;
LiteServerHealthRequest request;
WalletBackendStatus status;
std::string error;
};
std::vector<LiteServerEndpoint> defaultDragonXLiteServers();
LiteConnectionSettings defaultLiteConnectionSettings();
bool isLiteServerUrlUsable(const std::string& serverUrl);
const char* liteServerSelectionModeName(LiteServerSelectionMode mode);
const char* liteConnectionAvailabilityName(LiteConnectionAvailability availability);
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings);
class LiteConnectionService {
public:
LiteConnectionService(WalletCapabilities capabilities,
LiteConnectionSettings settings,
LiteClientBridge bridge);
const LiteConnectionSettings& settings() const { return settings_; }
const WalletCapabilities& capabilities() const { return capabilities_; }
LiteConnectionAvailability availability() const;
WalletBackendStatus status() const;
LiteServerSelectionResult selectedServer() const;
LiteWalletExistsRequest walletExistsRequest() const;
LiteServerHealthRequest serverHealthRequest() const;
LiteWalletExistsCheckResult checkWalletExists();
LiteServerHealthCheckResult checkSelectedServerHealth();
private:
WalletBackendStatus statusFor(LiteConnectionAvailability availability,
const std::string& detail = {}) const;
WalletBackendStatus bridgeReadinessStatus() const;
WalletCapabilities capabilities_;
LiteConnectionSettings settings_;
LiteClientBridge bridge_;
};
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,570 @@
#include "wallet/lite_result_parsers.h"
#include <algorithm>
#include <limits>
#include <stdexcept>
namespace dragonx::wallet {
namespace {
using json = nlohmann::json;
std::string fieldPath(const std::string& key)
{
return "$.'" + key + "'";
}
std::string indexPath(const std::string& parentPath, std::size_t index)
{
return parentPath + "[" + std::to_string(index) + "]";
}
template <typename Result>
Result makeResult(LiteResultCommand command)
{
Result result;
result.command = command;
return result;
}
template <typename Result>
bool fail(Result& result,
LiteResultParserError error,
const std::string& path,
const std::string& message)
{
result.ok = false;
result.error = error;
result.errorPath = path;
result.errorMessage = message;
result.issues.push_back({path, message});
return false;
}
template <typename Result>
void succeed(Result& result)
{
result.ok = true;
result.error = LiteResultParserError::None;
result.errorPath.clear();
result.errorMessage.clear();
}
template <typename Result>
bool parseJsonText(const std::string& jsonText, Result& result, json& parsed)
{
parsed = json::parse(jsonText, nullptr, false);
if (parsed.is_discarded()) {
return fail(result, LiteResultParserError::InvalidJson, "$", "response is not valid JSON");
}
return true;
}
template <typename Result>
bool requireObject(const json& value, const std::string& path, Result& result)
{
if (!value.is_object()) {
return fail(result, LiteResultParserError::ExpectedObject, path, "expected a JSON object");
}
return true;
}
template <typename Result>
bool requireArray(const json& value, const std::string& path, Result& result)
{
if (!value.is_array()) {
return fail(result, LiteResultParserError::ExpectedArray, path, "expected a JSON array");
}
return true;
}
bool jsonToUnsigned(const json& value, std::uint64_t& output)
{
try {
if (value.is_number_unsigned()) {
output = value.get<std::uint64_t>();
return true;
}
if (value.is_number_integer()) {
const auto signedValue = value.get<std::int64_t>();
if (signedValue < 0) return false;
output = static_cast<std::uint64_t>(signedValue);
return true;
}
if (value.is_string()) {
const auto text = value.get<std::string>();
if (text.empty()) return false;
std::size_t parsedSize = 0;
const auto parsed = std::stoull(text, &parsedSize, 10);
if (parsedSize != text.size()) return false;
output = static_cast<std::uint64_t>(parsed);
return true;
}
} catch (const std::exception&) {
return false;
}
return false;
}
bool jsonToSigned(const json& value, std::int64_t& output)
{
try {
if (value.is_number_integer()) {
output = value.get<std::int64_t>();
return true;
}
if (value.is_number_unsigned()) {
const auto unsignedValue = value.get<std::uint64_t>();
if (unsignedValue > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
return false;
}
output = static_cast<std::int64_t>(unsignedValue);
return true;
}
if (value.is_string()) {
const auto text = value.get<std::string>();
if (text.empty()) return false;
std::size_t parsedSize = 0;
const auto parsed = std::stoll(text, &parsedSize, 10);
if (parsedSize != text.size()) return false;
output = static_cast<std::int64_t>(parsed);
return true;
}
} catch (const std::exception&) {
return false;
}
return false;
}
template <typename Result>
bool readRequiredStringField(const json& object,
const std::string& key,
std::string& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) {
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required string field");
}
if (!object.at(key).is_string()) {
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
}
output = object.at(key).get<std::string>();
return true;
}
template <typename Result>
bool readOptionalStringField(const json& object,
const std::string& key,
std::optional<std::string>& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) return true;
if (!object.at(key).is_string()) {
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
}
output = object.at(key).get<std::string>();
return true;
}
template <typename Result>
bool readOptionalStringField(const json& object,
const std::string& key,
std::string& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) return true;
if (!object.at(key).is_string()) {
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
}
output = object.at(key).get<std::string>();
return true;
}
template <typename Result>
bool readRequiredSignedField(const json& object,
const std::string& key,
std::int64_t& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) {
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required integer field");
}
if (!jsonToSigned(object.at(key), output)) {
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
}
return true;
}
template <typename Result>
bool readRequiredUnsignedField(const json& object,
const std::string& key,
std::uint64_t& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) {
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required unsigned integer field");
}
if (!jsonToUnsigned(object.at(key), output)) {
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected an unsigned integer-compatible value");
}
return true;
}
template <typename Result>
bool readOptionalSignedField(const json& object,
const std::string& key,
std::optional<std::int64_t>& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) return true;
std::int64_t parsed = 0;
if (!jsonToSigned(object.at(key), parsed)) {
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
}
output = parsed;
return true;
}
template <typename Result>
bool readOptionalBoolField(const json& object,
const std::string& key,
bool& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) return true;
if (!object.at(key).is_boolean()) {
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a boolean");
}
output = object.at(key).get<bool>();
return true;
}
template <typename Result>
bool readStringArrayField(const json& object,
const std::string& key,
std::vector<std::string>& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) {
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required array field");
}
const auto& values = object.at(key);
if (!requireArray(values, fieldPath(key), result)) return false;
output.clear();
for (std::size_t index = 0; index < values.size(); ++index) {
if (!values[index].is_string()) {
return fail(result, LiteResultParserError::InvalidFieldType, indexPath(fieldPath(key), index), "expected a string array item");
}
output.push_back(values[index].get<std::string>());
}
return true;
}
template <typename Result>
bool parseSpendableOutput(const json& value,
LiteSpendableOutputKind kind,
const std::string& path,
LiteSpendableOutput& output,
Result& result)
{
if (!requireObject(value, path, result)) return false;
output.kind = kind;
output.pending = kind == LiteSpendableOutputKind::PendingNote || kind == LiteSpendableOutputKind::PendingUtxo;
if (!readRequiredStringField(value, "address", output.address, result)) return false;
if (!readRequiredStringField(value, "created_in_txid", output.createdInTxid, result)) return false;
if (!readOptionalSignedField(value, "created_in_block", output.createdInBlock, result)) return false;
if (!readRequiredUnsignedField(value, "value", output.value, result)) return false;
if (!readOptionalBoolField(value, "spent", output.spent, result)) return false;
if (!readOptionalBoolField(value, "unconfirmed_spent", output.unconfirmedSpent, result)) return false;
output.spendable = !output.pending && !output.spent && !output.unconfirmedSpent;
return true;
}
template <typename Result>
bool parseSpendableOutputArray(const json& object,
const std::string& key,
LiteSpendableOutputKind kind,
std::vector<LiteSpendableOutput>& output,
Result& result)
{
if (!object.contains(key) || object.at(key).is_null()) {
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required spendable output array");
}
const auto& values = object.at(key);
if (!requireArray(values, fieldPath(key), result)) return false;
output.clear();
for (std::size_t index = 0; index < values.size(); ++index) {
LiteSpendableOutput parsed;
if (!parseSpendableOutput(values[index], kind, indexPath(fieldPath(key), index), parsed, result)) return false;
output.push_back(std::move(parsed));
}
return true;
}
template <typename Result>
bool parseTransactionOutput(const json& value,
const std::string& path,
LiteTransactionOutput& output,
Result& result)
{
if (!requireObject(value, path, result)) return false;
if (!readRequiredStringField(value, "address", output.address, result)) return false;
if (!readRequiredSignedField(value, "value", output.value, result)) return false;
if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
return true;
}
template <typename Result>
bool parseTransactionRecord(const json& value,
const std::string& path,
LiteTransactionRecord& output,
Result& result)
{
if (!requireObject(value, path, result)) return false;
if (!readRequiredStringField(value, "txid", output.txid, result)) return false;
if (!readRequiredSignedField(value, "datetime", output.datetime, result)) return false;
if (!readOptionalSignedField(value, "block_height", output.blockHeight, result)) return false;
if (!readOptionalBoolField(value, "unconfirmed", output.unconfirmed, result)) return false;
if (value.contains("outgoing_metadata") && !value.at("outgoing_metadata").is_null()) {
const auto& metadata = value.at("outgoing_metadata");
if (!requireArray(metadata, path + ".outgoing_metadata", result)) return false;
output.direction = LiteTransactionDirection::Send;
for (std::size_t index = 0; index < metadata.size(); ++index) {
LiteTransactionOutput parsedOutput;
if (!parseTransactionOutput(metadata[index], path + ".outgoing_metadata[" + std::to_string(index) + "]", parsedOutput, result)) return false;
output.amount += parsedOutput.value;
output.outgoingMetadata.push_back(std::move(parsedOutput));
}
return true;
}
output.direction = LiteTransactionDirection::Receive;
if (!readRequiredStringField(value, "address", output.address, result)) return false;
if (!readRequiredSignedField(value, "amount", output.amount, result)) return false;
if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
if (!readOptionalSignedField(value, "position", output.position, result)) return false;
return true;
}
} // namespace
const char* liteResultCommandName(LiteResultCommand command)
{
switch (command) {
case LiteResultCommand::Info: return "info";
case LiteResultCommand::Height: return "height";
case LiteResultCommand::Balance: return "balance";
case LiteResultCommand::Addresses: return "addresses";
case LiteResultCommand::List: return "list";
case LiteResultCommand::Notes: return "notes";
case LiteResultCommand::SyncStatus: return "syncstatus";
}
return "unknown";
}
const char* liteResultParserErrorName(LiteResultParserError error)
{
switch (error) {
case LiteResultParserError::None: return "none";
case LiteResultParserError::InvalidJson: return "invalid_json";
case LiteResultParserError::ExpectedObject: return "expected_object";
case LiteResultParserError::ExpectedArray: return "expected_array";
case LiteResultParserError::MissingField: return "missing_field";
case LiteResultParserError::InvalidFieldType: return "invalid_field_type";
case LiteResultParserError::InvalidFieldValue: return "invalid_field_value";
}
return "unknown";
}
const char* liteSpendableOutputKindName(LiteSpendableOutputKind kind)
{
switch (kind) {
case LiteSpendableOutputKind::UnspentNote: return "unspent_note";
case LiteSpendableOutputKind::Utxo: return "utxo";
case LiteSpendableOutputKind::PendingNote: return "pending_note";
case LiteSpendableOutputKind::PendingUtxo: return "pending_utxo";
}
return "unknown";
}
const char* liteTransactionDirectionName(LiteTransactionDirection direction)
{
switch (direction) {
case LiteTransactionDirection::Unknown: return "unknown";
case LiteTransactionDirection::Send: return "send";
case LiteTransactionDirection::Receive: return "receive";
}
return "unknown";
}
LiteInfoParseResult parseLiteInfoResponse(const std::string& jsonText)
{
auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteInfoResponse(parsed);
}
LiteInfoParseResult parseLiteInfoResponse(const json& value)
{
auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
if (!requireObject(value, "$", result)) return result;
if (!readOptionalStringField(value, "chain_name", result.info.chainName, result)) return result;
if (!readOptionalStringField(value, "version", result.info.version, result)) return result;
if (!readOptionalStringField(value, "vendor", result.info.vendor, result)) return result;
if (!readOptionalSignedField(value, "latest_block_height", result.info.latestBlockHeight, result)) return result;
if (!readOptionalSignedField(value, "difficulty", result.info.difficulty, result)) return result;
if (!readOptionalSignedField(value, "longestchain", result.info.longestChain, result)) return result;
if (!readOptionalSignedField(value, "notarized", result.info.notarized, result)) return result;
if (!result.info.latestBlockHeight.has_value()) {
fail(result, LiteResultParserError::MissingField, fieldPath("latest_block_height"), "info response is missing latest_block_height");
return result;
}
succeed(result);
return result;
}
LiteHeightParseResult parseLiteHeightResponse(const std::string& jsonText)
{
auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteHeightResponse(parsed);
}
LiteHeightParseResult parseLiteHeightResponse(const json& value)
{
auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
std::int64_t parsedHeight = 0;
if (jsonToSigned(value, parsedHeight)) {
result.height.height = parsedHeight;
succeed(result);
return result;
}
if (!requireObject(value, "$", result)) return result;
for (const std::string key : {"height", "latest_block_height", "block_height"}) {
if (!value.contains(key) || value.at(key).is_null()) continue;
if (!jsonToSigned(value.at(key), parsedHeight)) {
fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "height field is not an integer-compatible value");
return result;
}
result.height.height = parsedHeight;
succeed(result);
return result;
}
fail(result, LiteResultParserError::MissingField, "$", "height response has no recognized height field");
return result;
}
LiteBalanceParseResult parseLiteBalanceResponse(const std::string& jsonText)
{
auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteBalanceResponse(parsed);
}
LiteBalanceParseResult parseLiteBalanceResponse(const json& value)
{
auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
if (!requireObject(value, "$", result)) return result;
if (!readRequiredUnsignedField(value, "tbalance", result.balance.transparentBalance, result)) return result;
if (!readRequiredUnsignedField(value, "zbalance", result.balance.shieldedBalance, result)) return result;
if (!readRequiredUnsignedField(value, "unconfirmed", result.balance.unconfirmedBalance, result)) return result;
if (!readRequiredUnsignedField(value, "verified_zbalance", result.balance.verifiedShieldedBalance, result)) return result;
if (!readRequiredUnsignedField(value, "spendable_zbalance", result.balance.spendableShieldedBalance, result)) return result;
succeed(result);
return result;
}
LiteAddressesParseResult parseLiteAddressesResponse(const std::string& jsonText)
{
auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteAddressesResponse(parsed);
}
LiteAddressesParseResult parseLiteAddressesResponse(const json& value)
{
auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
if (!requireObject(value, "$", result)) return result;
if (!readStringArrayField(value, "z_addresses", result.addresses.zAddresses, result)) return result;
if (!readStringArrayField(value, "t_addresses", result.addresses.tAddresses, result)) return result;
succeed(result);
return result;
}
LiteNotesParseResult parseLiteNotesResponse(const std::string& jsonText)
{
auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteNotesResponse(parsed);
}
LiteNotesParseResult parseLiteNotesResponse(const json& value)
{
auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
if (!requireObject(value, "$", result)) return result;
if (!parseSpendableOutputArray(value, "unspent_notes", LiteSpendableOutputKind::UnspentNote, result.notes.unspentNotes, result)) return result;
if (!parseSpendableOutputArray(value, "utxos", LiteSpendableOutputKind::Utxo, result.notes.utxos, result)) return result;
if (!parseSpendableOutputArray(value, "pending_notes", LiteSpendableOutputKind::PendingNote, result.notes.pendingNotes, result)) return result;
if (!parseSpendableOutputArray(value, "pending_utxos", LiteSpendableOutputKind::PendingUtxo, result.notes.pendingUtxos, result)) return result;
succeed(result);
return result;
}
LiteTransactionsParseResult parseLiteTransactionsResponse(const std::string& jsonText)
{
auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteTransactionsResponse(parsed);
}
LiteTransactionsParseResult parseLiteTransactionsResponse(const json& value)
{
auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
if (!requireArray(value, "$", result)) return result;
result.transactions.transactions.clear();
for (std::size_t index = 0; index < value.size(); ++index) {
LiteTransactionRecord record;
if (!parseTransactionRecord(value[index], "$[" + std::to_string(index) + "]", record, result)) return result;
result.transactions.transactions.push_back(std::move(record));
}
succeed(result);
return result;
}
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const std::string& jsonText)
{
auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
json parsed;
if (!parseJsonText(jsonText, result, parsed)) return result;
return parseLiteSyncStatusResponse(parsed);
}
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const json& value)
{
auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
if (!requireObject(value, "$", result)) return result;
if (!readRequiredUnsignedField(value, "synced_blocks", result.syncStatus.syncedBlocks, result)) return result;
if (!readRequiredUnsignedField(value, "total_blocks", result.syncStatus.totalBlocks, result)) return result;
if (result.syncStatus.totalBlocks > 0) {
result.syncStatus.progress = std::min(1.0, static_cast<double>(result.syncStatus.syncedBlocks) / static_cast<double>(result.syncStatus.totalBlocks));
result.syncStatus.complete = result.syncStatus.syncedBlocks >= result.syncStatus.totalBlocks;
}
succeed(result);
return result;
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,169 @@
#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;
};
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);
} // namespace dragonx::wallet

View File

@@ -0,0 +1,371 @@
#include "wallet/lite_sync_service.h"
#include <algorithm>
#include <cctype>
#include <limits>
#include <utility>
namespace dragonx::wallet {
namespace {
std::string trimSyncCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
std::uint64_t effectiveStagnantThreshold(std::uint64_t threshold)
{
return threshold == 0 ? 1 : threshold;
}
WalletBackendStatus makeSyncScaffoldStatus(WalletBackendState state,
std::string message,
const LiteSyncStatusResponse* syncStatus = nullptr)
{
WalletBackendStatus status;
status.state = state;
status.message = std::move(message);
if (syncStatus) {
status.walletHeight = static_cast<int>(std::min<std::uint64_t>(syncStatus->syncedBlocks, static_cast<std::uint64_t>(std::numeric_limits<int>::max())));
status.chainHeight = static_cast<int>(std::min<std::uint64_t>(syncStatus->totalBlocks, static_cast<std::uint64_t>(std::numeric_limits<int>::max())));
status.syncProgress = syncStatus->progress;
}
return status;
}
} // namespace
const char* liteSyncOperationName(LiteSyncOperation operation)
{
switch (operation) {
case LiteSyncOperation::StartSync: return "StartSync";
case LiteSyncOperation::PollSyncStatus: return "PollSyncStatus";
}
return "Unknown";
}
const char* liteSyncStartModeName(LiteSyncStartMode mode)
{
switch (mode) {
case LiteSyncStartMode::Startup: return "Startup";
case LiteSyncStartMode::Restore: return "Restore";
case LiteSyncStartMode::Rescan: return "Rescan";
case LiteSyncStartMode::Recovery: return "Recovery";
}
return "Unknown";
}
const char* liteSyncAvailabilityName(LiteSyncAvailability availability)
{
switch (availability) {
case LiteSyncAvailability::Ready: return "Ready";
case LiteSyncAvailability::UnsupportedBuild: return "UnsupportedBuild";
case LiteSyncAvailability::BackendUnavailable: return "BackendUnavailable";
case LiteSyncAvailability::BridgeUnavailable: return "BridgeUnavailable";
case LiteSyncAvailability::BridgeCallsDisabled: return "BridgeCallsDisabled";
case LiteSyncAvailability::NoUsableServer: return "NoUsableServer";
}
return "Unknown";
}
const char* liteSyncRecoveryDecisionKindName(LiteSyncRecoveryDecisionKind kind)
{
switch (kind) {
case LiteSyncRecoveryDecisionKind::KeepPolling: return "KeepPolling";
case LiteSyncRecoveryDecisionKind::SyncComplete: return "SyncComplete";
case LiteSyncRecoveryDecisionKind::Stuck: return "Stuck";
case LiteSyncRecoveryDecisionKind::ReorgDetected: return "ReorgDetected";
case LiteSyncRecoveryDecisionKind::InvalidStatus: return "InvalidStatus";
}
return "Unknown";
}
WalletBackendStatus walletStatusFromLiteSyncStatus(const LiteSyncStatusResponse& syncStatus)
{
if (syncStatus.complete) {
return makeSyncScaffoldStatus(
WalletBackendState::Ready,
"lite syncstatus reports sync complete",
&syncStatus);
}
return makeSyncScaffoldStatus(
WalletBackendState::Syncing,
"lite syncstatus reports sync in progress",
&syncStatus);
}
LiteSyncRecoveryDecision evaluateLiteSyncRecovery(const LiteSyncRecoveryInput& input)
{
if (input.totalBlocks > 0 && input.syncedBlocks > input.totalBlocks) {
return LiteSyncRecoveryDecision{
LiteSyncRecoveryDecisionKind::InvalidStatus,
false,
false,
false,
false,
true,
"synced block count is greater than total block count"
};
}
if (input.havePreviousSyncedBlocks && input.syncedBlocks < input.previousSyncedBlocks) {
return LiteSyncRecoveryDecision{
LiteSyncRecoveryDecisionKind::ReorgDetected,
false,
true,
true,
true,
false,
"synced block count moved backwards; model clear/rescan recovery"
};
}
if (input.totalBlocks > 0 && input.syncedBlocks >= input.totalBlocks) {
return LiteSyncRecoveryDecision{
LiteSyncRecoveryDecisionKind::SyncComplete,
false,
false,
false,
false,
false,
"syncstatus reports all blocks synced"
};
}
const auto threshold = effectiveStagnantThreshold(input.stagnantPollThreshold);
if (input.havePreviousSyncedBlocks &&
input.syncedBlocks == input.previousSyncedBlocks &&
input.stagnantPollCount >= threshold) {
return LiteSyncRecoveryDecision{
LiteSyncRecoveryDecisionKind::Stuck,
false,
false,
false,
true,
false,
"synced block count has not advanced past the configured threshold"
};
}
return LiteSyncRecoveryDecision{
LiteSyncRecoveryDecisionKind::KeepPolling,
true,
false,
false,
false,
false,
"syncstatus is usable; keep polling"
};
}
LiteSyncService::LiteSyncService(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge bridge,
LiteSyncServiceOptions options)
: capabilities_(capabilities),
connectionSettings_(std::move(connectionSettings)),
bridge_(std::move(bridge)),
options_(options)
{
}
LiteSyncAvailability LiteSyncService::availability() const
{
if (!isLiteBuild(capabilities_)) return LiteSyncAvailability::UnsupportedBuild;
if (!supportsLiteBackend(capabilities_)) return LiteSyncAvailability::BackendUnavailable;
if (!bridge_.available()) return LiteSyncAvailability::BridgeUnavailable;
if (!selectLiteServer(connectionSettings_).ok) return LiteSyncAvailability::NoUsableServer;
if (!options_.allowSyncStatusBridgeCalls) return LiteSyncAvailability::BridgeCallsDisabled;
return LiteSyncAvailability::Ready;
}
WalletBackendStatus LiteSyncService::status() const
{
const auto currentAvailability = availability();
if (currentAvailability == LiteSyncAvailability::NoUsableServer) {
return statusFor(currentAvailability, selectLiteServer(connectionSettings_).error);
}
return statusFor(currentAvailability);
}
LiteSyncPlan LiteSyncService::planStartSync(const LiteSyncStartRequest& request) const
{
return makePlan(
LiteSyncOperation::StartSync,
request.serverUrl,
false,
request.mode,
request.forceRescan,
request.afterRestore);
}
LiteSyncPlan LiteSyncService::planSyncStatus(const LiteSyncStatusRequest& request) const
{
return makePlan(
LiteSyncOperation::PollSyncStatus,
request.serverUrl,
options_.allowSyncStatusBridgeCalls,
LiteSyncStartMode::Startup,
false,
false);
}
LiteSyncStartResult LiteSyncService::startSync(const LiteSyncStartRequest& request)
{
const auto plan = planStartSync(request);
if (!plan.ok) return blockedStartResult(plan, makeSyncScaffoldStatus(WalletBackendState::Error, plan.error));
const auto currentAvailability = availability();
if (currentAvailability == LiteSyncAvailability::UnsupportedBuild ||
currentAvailability == LiteSyncAvailability::BackendUnavailable ||
currentAvailability == LiteSyncAvailability::BridgeUnavailable ||
currentAvailability == LiteSyncAvailability::NoUsableServer) {
return blockedStartResult(plan, status());
}
return blockedStartResult(
plan,
makeSyncScaffoldStatus(WalletBackendState::Unavailable, "lite sync start execution is not implemented"));
}
LiteSyncStatusResult LiteSyncService::pollSyncStatus(const LiteSyncStatusRequest& request)
{
const auto plan = planSyncStatus(request);
if (!plan.ok) return blockedStatusResult(plan, makeSyncScaffoldStatus(WalletBackendState::Error, plan.error));
if (availability() != LiteSyncAvailability::Ready) return blockedStatusResult(plan, status());
LiteSyncStatusResult result;
result.plan = plan;
result.attempted = true;
const auto bridgeCall = bridge_.execute(plan.command, plan.args);
result.bridgeResponseRedacted = bridgeCall.ok || !bridgeCall.error.empty() ? "<redacted>" : "<empty>";
if (!bridgeCall.ok) {
result.status = makeSyncScaffoldStatus(WalletBackendState::Error, "lite syncstatus bridge call failed");
result.error = result.status.message;
return result;
}
result.bridgeAccepted = true;
const auto parsed = parseLiteSyncStatusResponse(bridgeCall.value);
if (!parsed.ok) {
result.parserError = parsed.error;
result.status = makeSyncScaffoldStatus(WalletBackendState::Error, "lite syncstatus response could not be parsed");
result.error = result.status.message;
return result;
}
result.ok = true;
result.parserError = LiteResultParserError::None;
result.syncStatus = parsed.syncStatus;
result.status = walletStatusFromLiteSyncStatus(result.syncStatus);
return result;
}
LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const
{
const auto overrideUrl = trimSyncCopy(serverUrl);
if (!overrideUrl.empty()) {
if (!isLiteServerUrlUsable(overrideUrl)) {
return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"};
}
return LiteServerSelectionResult{
true,
LiteServerEndpoint{overrideUrl, "Request", true},
0,
true,
{}
};
}
return selectLiteServer(connectionSettings_);
}
LiteSyncPlan LiteSyncService::makePlan(LiteSyncOperation operation,
const std::string& serverUrl,
bool bridgeExecutionAllowed,
LiteSyncStartMode startMode,
bool forceRescan,
bool afterRestore) const
{
LiteSyncPlan plan;
plan.operation = operation;
plan.startMode = startMode;
plan.bridgeExecutionAllowed = bridgeExecutionAllowed;
plan.forceRescan = forceRescan;
plan.afterRestore = afterRestore;
plan.command = operation == LiteSyncOperation::StartSync ? "sync" : "syncstatus";
auto selection = selectServerForRequest(serverUrl);
if (!selection.ok) {
plan.error = selection.error;
return plan;
}
plan.ok = true;
plan.server = selection.server;
plan.serverIndex = selection.serverIndex;
plan.customServer = selection.customServer;
return plan;
}
WalletBackendStatus LiteSyncService::statusFor(LiteSyncAvailability availability,
const std::string& detail) const
{
switch (availability) {
case LiteSyncAvailability::Ready:
return makeSyncScaffoldStatus(
WalletBackendState::Disconnected,
detail.empty() ? "lite sync scaffold ready; sync is not started" : detail);
case LiteSyncAvailability::UnsupportedBuild:
return makeSyncScaffoldStatus(
WalletBackendState::Unavailable,
"lite sync service is unsupported in full-node builds");
case LiteSyncAvailability::BackendUnavailable:
return makeSyncScaffoldStatus(
WalletBackendState::Unavailable,
"lite backend is not linked");
case LiteSyncAvailability::BridgeUnavailable:
return makeSyncScaffoldStatus(
WalletBackendState::Unavailable,
detail.empty() ? bridge_.unavailableReason() : detail);
case LiteSyncAvailability::BridgeCallsDisabled:
return makeSyncScaffoldStatus(
WalletBackendState::Unavailable,
"lite syncstatus bridge calls are disabled");
case LiteSyncAvailability::NoUsableServer:
return makeSyncScaffoldStatus(
WalletBackendState::Error,
detail.empty() ? "no usable lite servers are configured" : detail);
}
return makeSyncScaffoldStatus(WalletBackendState::Unavailable, "unknown lite sync state");
}
LiteSyncStartResult LiteSyncService::blockedStartResult(const LiteSyncPlan& plan,
const WalletBackendStatus& blockedStatus) const
{
LiteSyncStartResult result;
result.plan = plan;
result.status = blockedStatus;
result.error = blockedStatus.message;
return result;
}
LiteSyncStatusResult LiteSyncService::blockedStatusResult(const LiteSyncPlan& plan,
const WalletBackendStatus& blockedStatus) const
{
LiteSyncStatusResult result;
result.plan = plan;
result.status = blockedStatus;
result.error = blockedStatus.message;
return result;
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,162 @@
#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_;
LiteSyncServiceOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,172 @@
#include "wallet/lite_wallet_app_refresh_coordinator.h"
#include <utility>
namespace dragonx::wallet {
namespace {
void addIssue(LiteWalletAppRefreshCoordinationResult& result,
LiteWalletAppRefreshCoordinationIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletAppRefreshCoordinationIssueInfo{issue, std::move(message)});
}
std::string refreshFailureMessage(const LiteWalletRefreshServiceResult& refreshResult)
{
if (!refreshResult.error.empty()) return refreshResult.error;
if (!refreshResult.status.message.empty()) return refreshResult.status.message;
return "lite wallet refresh service result is not successful";
}
void copyExecutionSummary(LiteWalletAppRefreshCoordinationResult& result)
{
result.stateMutationRequested = result.executionResult.stateMutationRequested;
result.stateMutationAllowed = result.applyPlan.stateMutationAllowed ||
result.executionResult.stateMutationAllowed;
result.stateMutated = result.executionResult.stateMutated;
result.walletStateWritten = result.stateMutated;
result.dryRunOnly = result.applyPlan.dryRunOnly && result.executionResult.dryRunOnly;
result.noNetwork = result.executionResult.noNetwork;
result.fieldPlanCount = result.executionResult.fieldPlanCount;
result.collectionPlanCount = result.executionResult.collectionPlanCount;
result.plannedChangeCount = result.executionResult.plannedChangeCount;
result.planIssueCount = result.executionResult.planIssueCount;
result.executionIssueCount = result.executionResult.issues.size();
}
} // namespace
const char* liteWalletAppRefreshCoordinationStatusName(
LiteWalletAppRefreshCoordinationStatus status)
{
switch (status) {
case LiteWalletAppRefreshCoordinationStatus::DryRunReported: return "DryRunReported";
case LiteWalletAppRefreshCoordinationStatus::Rejected: return "Rejected";
case LiteWalletAppRefreshCoordinationStatus::ApplyUnavailable: return "ApplyUnavailable";
}
return "Unknown";
}
const char* liteWalletAppRefreshCoordinationIssueName(
LiteWalletAppRefreshCoordinationIssue issue)
{
switch (issue) {
case LiteWalletAppRefreshCoordinationIssue::RefreshResultFailed: return "RefreshResultFailed";
case LiteWalletAppRefreshCoordinationIssue::FullNodeRefreshDelegated: return "FullNodeRefreshDelegated";
case LiteWalletAppRefreshCoordinationIssue::MappingFailed: return "MappingFailed";
case LiteWalletAppRefreshCoordinationIssue::ApplyPlanFailed: return "ApplyPlanFailed";
case LiteWalletAppRefreshCoordinationIssue::ApplyExecutionRejected: return "ApplyExecutionRejected";
case LiteWalletAppRefreshCoordinationIssue::StateMutationDisabled: return "StateMutationDisabled";
case LiteWalletAppRefreshCoordinationIssue::StateMutationImplementationMissing: return "StateMutationImplementationMissing";
}
return "Unknown";
}
LiteWalletAppRefreshCoordinationResult coordinateLiteWalletAppRefresh(
const LiteWalletRefreshServiceResult& refreshResult,
const dragonx::WalletState& walletState,
LiteWalletAppRefreshCoordinatorOptions options)
{
LiteWalletAppRefreshCoordinationResult result;
result.route = refreshResult.plan.route;
result.refreshStatus = refreshResult.status;
result.refreshAttempted = refreshResult.attempted;
result.fullNodeRefreshDelegated = refreshResult.fullNodeRefreshDelegated;
result.liteGatewayCalled = refreshResult.liteGatewayCalled;
result.stateMutationRequested = options.executorOptions.requestStateMutation;
if (refreshResult.fullNodeRefreshDelegated) {
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::FullNodeRefreshDelegated,
"full-node refresh remains delegated; lite app refresh coordinator did not run");
result.error = result.issues.back().message;
return result;
}
if (!refreshResult.ok) {
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::RefreshResultFailed,
refreshFailureMessage(refreshResult));
result.error = result.issues.back().message;
return result;
}
result.refreshAccepted = true;
result.mapResult = mapLiteWalletRefreshServiceResult(refreshResult);
result.mapIssueCount = result.mapResult.issues.size();
if (!result.mapResult.ok) {
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::MappingFailed,
result.mapResult.error.empty()
? "lite wallet refresh service result could not be mapped"
: result.mapResult.error);
result.error = result.issues.back().message;
return result;
}
result.mapped = true;
result.successfulCommandCount = result.mapResult.model.successfulCommandCount;
result.applyPlan = planLiteWalletStateApply(result.mapResult.model, walletState);
if (!result.applyPlan.ok) {
result.fieldPlanCount = result.applyPlan.fieldPlans.size();
result.collectionPlanCount = result.applyPlan.collectionPlans.size();
result.planIssueCount = result.applyPlan.issues.size();
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::ApplyPlanFailed,
result.applyPlan.error.empty()
? "lite WalletState apply plan could not be built"
: result.applyPlan.error);
result.error = result.issues.back().message;
return result;
}
result.planned = true;
LiteWalletStateApplyExecutor executor(options.executorOptions);
result.executionResult = executor.execute(result.applyPlan);
result.executionReported = true;
copyExecutionSummary(result);
if (!result.executionResult.ok) {
result.error = result.executionResult.error;
if (result.executionResult.status == LiteWalletStateApplyExecutionStatus::ImplementationMissing) {
result.status = LiteWalletAppRefreshCoordinationStatus::ApplyUnavailable;
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::StateMutationImplementationMissing,
result.executionResult.error.empty()
? "real lite WalletState application requires a future explicit implementation"
: result.executionResult.error);
} else {
result.status = LiteWalletAppRefreshCoordinationStatus::Rejected;
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::ApplyExecutionRejected,
result.executionResult.error.empty()
? "lite WalletState apply execution report was rejected"
: result.executionResult.error);
}
if (result.error.empty()) result.error = result.issues.back().message;
return result;
}
result.ok = true;
result.status = LiteWalletAppRefreshCoordinationStatus::DryRunReported;
addIssue(result,
LiteWalletAppRefreshCoordinationIssue::StateMutationDisabled,
"lite app refresh coordinator produced a dry-run report only; WalletState was not written");
return result;
}
LiteWalletAppRefreshCoordinator::LiteWalletAppRefreshCoordinator(
LiteWalletAppRefreshCoordinatorOptions options)
: options_(options)
{
}
LiteWalletAppRefreshCoordinationResult LiteWalletAppRefreshCoordinator::coordinate(
const LiteWalletRefreshServiceResult& refreshResult,
const dragonx::WalletState& walletState) const
{
return coordinateLiteWalletAppRefresh(refreshResult, walletState, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,96 @@
#pragma once
#include "data/wallet_state.h"
#include "lite_wallet_refresh_service.h"
#include "lite_wallet_state_apply_executor.h"
#include "lite_wallet_state_mapper.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletAppRefreshCoordinationStatus {
DryRunReported,
Rejected,
ApplyUnavailable,
};
enum class LiteWalletAppRefreshCoordinationIssue {
RefreshResultFailed,
FullNodeRefreshDelegated,
MappingFailed,
ApplyPlanFailed,
ApplyExecutionRejected,
StateMutationDisabled,
StateMutationImplementationMissing,
};
struct LiteWalletAppRefreshCoordinatorOptions {
LiteWalletStateApplyExecutorOptions executorOptions;
};
struct LiteWalletAppRefreshCoordinationIssueInfo {
LiteWalletAppRefreshCoordinationIssue issue = LiteWalletAppRefreshCoordinationIssue::StateMutationDisabled;
std::string message;
};
struct LiteWalletAppRefreshCoordinationResult {
bool ok = false;
bool refreshAccepted = false;
bool refreshAttempted = false;
bool fullNodeRefreshDelegated = false;
bool liteGatewayCalled = false;
bool mapped = false;
bool planned = false;
bool executionReported = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool stateMutationRequested = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
LiteWalletAppRefreshCoordinationStatus status = LiteWalletAppRefreshCoordinationStatus::Rejected;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
WalletBackendStatus refreshStatus;
std::size_t successfulCommandCount = 0;
std::size_t mapIssueCount = 0;
std::size_t fieldPlanCount = 0;
std::size_t collectionPlanCount = 0;
std::size_t plannedChangeCount = 0;
std::size_t planIssueCount = 0;
std::size_t executionIssueCount = 0;
LiteWalletStateMapResult mapResult;
LiteWalletStateApplyPlan applyPlan;
LiteWalletStateApplyExecutionResult executionResult;
std::vector<LiteWalletAppRefreshCoordinationIssueInfo> issues;
std::string error;
};
const char* liteWalletAppRefreshCoordinationStatusName(
LiteWalletAppRefreshCoordinationStatus status);
const char* liteWalletAppRefreshCoordinationIssueName(
LiteWalletAppRefreshCoordinationIssue issue);
LiteWalletAppRefreshCoordinationResult coordinateLiteWalletAppRefresh(
const LiteWalletRefreshServiceResult& refreshResult,
const dragonx::WalletState& walletState,
LiteWalletAppRefreshCoordinatorOptions options = {});
class LiteWalletAppRefreshCoordinator {
public:
explicit LiteWalletAppRefreshCoordinator(LiteWalletAppRefreshCoordinatorOptions options = {});
LiteWalletAppRefreshCoordinationResult coordinate(
const LiteWalletRefreshServiceResult& refreshResult,
const dragonx::WalletState& walletState) const;
private:
LiteWalletAppRefreshCoordinatorOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,197 @@
#include "wallet/lite_wallet_app_refresh_orchestrator.h"
#include <utility>
namespace dragonx::wallet {
namespace {
void addIssue(LiteWalletAppRefreshOrchestrationResult& result,
LiteWalletAppRefreshOrchestrationIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletAppRefreshOrchestrationIssueInfo{issue, std::move(message)});
}
bool readinessReportIsUnsafe(const LiteWalletRefreshReadinessResult& readiness)
{
return !readiness.dryRunOnly ||
!readiness.noNetwork ||
readiness.stateMutationRequested ||
readiness.stateMutationAllowed ||
readiness.stateMutated ||
readiness.walletStateWritten;
}
std::string readinessErrorOrDefault(const LiteWalletRefreshReadinessResult& readiness)
{
return readiness.error.empty()
? "lite refresh readiness report is not eligible"
: readiness.error;
}
LiteWalletAppRefreshOrchestrationResult blockedResult(
LiteWalletAppRefreshOrchestrationResult result,
LiteWalletAppRefreshOrchestrationIssue issue,
std::string message)
{
result.status = LiteWalletAppRefreshOrchestrationStatus::Blocked;
result.blocked = true;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
return result;
}
LiteWalletAppRefreshOrchestrationResult skippedResult(
LiteWalletAppRefreshOrchestrationResult result,
LiteWalletAppRefreshOrchestrationIssue issue,
std::string message)
{
result.ok = true;
result.status = LiteWalletAppRefreshOrchestrationStatus::Skipped;
result.skipped = true;
addIssue(result, issue, std::move(message));
return result;
}
std::size_t effectiveMaxQueueDepth(const LiteWalletAppRefreshScheduleInput& schedule,
const LiteWalletAppRefreshOrchestratorOptions& options)
{
return schedule.maxQueueDepth > 0 ? schedule.maxQueueDepth : options.maxQueueDepth;
}
} // namespace
const char* liteWalletAppRefreshOrchestrationStatusName(
LiteWalletAppRefreshOrchestrationStatus status)
{
switch (status) {
case LiteWalletAppRefreshOrchestrationStatus::Queued: return "Queued";
case LiteWalletAppRefreshOrchestrationStatus::Skipped: return "Skipped";
case LiteWalletAppRefreshOrchestrationStatus::Blocked: return "Blocked";
}
return "Unknown";
}
const char* liteWalletAppRefreshScheduleTriggerName(
LiteWalletAppRefreshScheduleTrigger trigger)
{
switch (trigger) {
case LiteWalletAppRefreshScheduleTrigger::Periodic: return "Periodic";
case LiteWalletAppRefreshScheduleTrigger::Manual: return "Manual";
case LiteWalletAppRefreshScheduleTrigger::Startup: return "Startup";
case LiteWalletAppRefreshScheduleTrigger::WalletOpened: return "WalletOpened";
}
return "Unknown";
}
const char* liteWalletAppRefreshOrchestrationIssueName(
LiteWalletAppRefreshOrchestrationIssue issue)
{
switch (issue) {
case LiteWalletAppRefreshOrchestrationIssue::ReadinessRejected: return "ReadinessRejected";
case LiteWalletAppRefreshOrchestrationIssue::UnsafeReadinessReport: return "UnsafeReadinessReport";
case LiteWalletAppRefreshOrchestrationIssue::SchedulerDisabled: return "SchedulerDisabled";
case LiteWalletAppRefreshOrchestrationIssue::RefreshNotDue: return "RefreshNotDue";
case LiteWalletAppRefreshOrchestrationIssue::RefreshAlreadyQueued: return "RefreshAlreadyQueued";
case LiteWalletAppRefreshOrchestrationIssue::RefreshInProgress: return "RefreshInProgress";
case LiteWalletAppRefreshOrchestrationIssue::QueuePressure: return "QueuePressure";
}
return "Unknown";
}
LiteWalletAppRefreshOrchestrationResult orchestrateLiteWalletAppRefresh(
const LiteWalletRefreshReadinessResult& readiness,
const LiteWalletAppRefreshScheduleInput& schedule,
LiteWalletAppRefreshOrchestratorOptions options)
{
LiteWalletAppRefreshOrchestrationResult result;
result.readinessResult = readiness;
result.route = readiness.route;
result.dryRunOnly = readiness.dryRunOnly;
result.noNetwork = readiness.noNetwork;
result.stateMutationRequested = readiness.stateMutationRequested;
result.stateMutationAllowed = readiness.stateMutationAllowed;
result.stateMutated = readiness.stateMutated;
result.walletStateWritten = readiness.walletStateWritten;
result.trigger = schedule.trigger;
result.schedulerEnabled = schedule.schedulerEnabled;
result.refreshDue = schedule.refreshDue;
result.forceRefresh = schedule.forceRefresh;
result.refreshAlreadyQueued = schedule.refreshAlreadyQueued;
result.refreshInProgress = schedule.refreshInProgress;
result.queueDepth = schedule.queueDepth;
result.maxQueueDepth = effectiveMaxQueueDepth(schedule, options);
if (options.requireEligibleReadiness && (!readiness.ok || !readiness.eligibleForFutureUiRefresh)) {
return blockedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::ReadinessRejected,
readinessErrorOrDefault(readiness));
}
if (readinessReportIsUnsafe(readiness)) {
return blockedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::UnsafeReadinessReport,
"lite refresh readiness report is not a no-network dry-run report");
}
result.readinessAccepted = true;
if (!schedule.schedulerEnabled) {
return blockedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::SchedulerDisabled,
"lite app refresh scheduler is disabled");
}
result.schedulerAccepted = true;
if (result.maxQueueDepth > 0 && schedule.queueDepth >= result.maxQueueDepth) {
return blockedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::QueuePressure,
"lite app refresh queue is at capacity");
}
if (schedule.refreshAlreadyQueued) {
return skippedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::RefreshAlreadyQueued,
"lite app refresh is already queued");
}
if (schedule.refreshInProgress) {
return skippedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::RefreshInProgress,
"lite app refresh is already in progress");
}
if (!schedule.refreshDue && !schedule.forceRefresh) {
return skippedResult(
std::move(result),
LiteWalletAppRefreshOrchestrationIssue::RefreshNotDue,
"lite app refresh is not due");
}
result.ok = true;
result.status = LiteWalletAppRefreshOrchestrationStatus::Queued;
result.wouldQueueRefresh = true;
return result;
}
LiteWalletAppRefreshOrchestrator::LiteWalletAppRefreshOrchestrator(
LiteWalletAppRefreshOrchestratorOptions options)
: options_(options)
{
}
LiteWalletAppRefreshOrchestrationResult LiteWalletAppRefreshOrchestrator::evaluate(
const LiteWalletRefreshReadinessResult& readiness,
const LiteWalletAppRefreshScheduleInput& schedule) const
{
return orchestrateLiteWalletAppRefresh(readiness, schedule, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,109 @@
#pragma once
#include "lite_wallet_refresh_readiness_policy.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletAppRefreshOrchestrationStatus {
Queued,
Skipped,
Blocked,
};
enum class LiteWalletAppRefreshScheduleTrigger {
Periodic,
Manual,
Startup,
WalletOpened,
};
enum class LiteWalletAppRefreshOrchestrationIssue {
ReadinessRejected,
UnsafeReadinessReport,
SchedulerDisabled,
RefreshNotDue,
RefreshAlreadyQueued,
RefreshInProgress,
QueuePressure,
};
struct LiteWalletAppRefreshScheduleInput {
LiteWalletAppRefreshScheduleTrigger trigger = LiteWalletAppRefreshScheduleTrigger::Periodic;
bool schedulerEnabled = true;
bool refreshDue = false;
bool forceRefresh = false;
bool refreshAlreadyQueued = false;
bool refreshInProgress = false;
std::size_t queueDepth = 0;
std::size_t maxQueueDepth = 0;
};
struct LiteWalletAppRefreshOrchestratorOptions {
bool requireEligibleReadiness = true;
std::size_t maxQueueDepth = 0;
};
struct LiteWalletAppRefreshOrchestrationIssueInfo {
LiteWalletAppRefreshOrchestrationIssue issue = LiteWalletAppRefreshOrchestrationIssue::ReadinessRejected;
std::string message;
};
struct LiteWalletAppRefreshOrchestrationResult {
bool ok = false;
bool wouldQueueRefresh = false;
bool skipped = false;
bool blocked = false;
bool readinessAccepted = false;
bool schedulerAccepted = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool stateMutationRequested = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool schedulerEnabled = true;
bool refreshDue = false;
bool forceRefresh = false;
bool refreshAlreadyQueued = false;
bool refreshInProgress = false;
std::size_t queueDepth = 0;
std::size_t maxQueueDepth = 0;
LiteWalletAppRefreshOrchestrationStatus status = LiteWalletAppRefreshOrchestrationStatus::Blocked;
LiteWalletAppRefreshScheduleTrigger trigger = LiteWalletAppRefreshScheduleTrigger::Periodic;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
LiteWalletRefreshReadinessResult readinessResult;
std::vector<LiteWalletAppRefreshOrchestrationIssueInfo> issues;
std::string error;
};
const char* liteWalletAppRefreshOrchestrationStatusName(
LiteWalletAppRefreshOrchestrationStatus status);
const char* liteWalletAppRefreshScheduleTriggerName(
LiteWalletAppRefreshScheduleTrigger trigger);
const char* liteWalletAppRefreshOrchestrationIssueName(
LiteWalletAppRefreshOrchestrationIssue issue);
LiteWalletAppRefreshOrchestrationResult orchestrateLiteWalletAppRefresh(
const LiteWalletRefreshReadinessResult& readiness,
const LiteWalletAppRefreshScheduleInput& schedule,
LiteWalletAppRefreshOrchestratorOptions options = {});
class LiteWalletAppRefreshOrchestrator {
public:
explicit LiteWalletAppRefreshOrchestrator(LiteWalletAppRefreshOrchestratorOptions options = {});
LiteWalletAppRefreshOrchestrationResult evaluate(
const LiteWalletRefreshReadinessResult& readiness,
const LiteWalletAppRefreshScheduleInput& schedule) const;
private:
LiteWalletAppRefreshOrchestratorOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,409 @@
#include "wallet/lite_wallet_gateway.h"
#include <cctype>
#include <utility>
namespace dragonx::wallet {
namespace {
std::string trimGatewayCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
WalletBackendStatus makeGatewayStatus(WalletBackendState state, std::string message)
{
WalletBackendStatus status;
status.state = state;
status.message = std::move(message);
return status;
}
WalletBackendStatus refreshStatusForRequest(const LiteWalletRefreshRequest& request)
{
if (request.haveSyncStatus) return walletStatusFromLiteSyncStatus(request.syncStatus);
return makeGatewayStatus(
WalletBackendState::Disconnected,
"lite wallet refresh bundle assembled; runtime wallet state is unchanged");
}
LiteWalletGatewayCommandResult parsedCommandResult(const LiteWalletGatewayPlan& plan)
{
LiteWalletGatewayCommandResult result;
result.ok = true;
result.attempted = true;
result.bridgeAccepted = true;
result.plan = plan;
result.parserError = LiteResultParserError::None;
result.status = makeGatewayStatus(
WalletBackendState::Disconnected,
"lite gateway response parsed into intermediate refresh model");
result.bridgeResponseRedacted = "<redacted>";
return result;
}
} // namespace
const char* liteWalletGatewayCommandName(LiteWalletGatewayCommand command)
{
switch (command) {
case LiteWalletGatewayCommand::Info: return "info";
case LiteWalletGatewayCommand::Height: return "height";
case LiteWalletGatewayCommand::Balance: return "balance";
case LiteWalletGatewayCommand::Addresses: return "addresses";
case LiteWalletGatewayCommand::Notes: return "notes";
case LiteWalletGatewayCommand::List: return "list";
}
return "unknown";
}
const char* liteWalletGatewayAvailabilityName(LiteWalletGatewayAvailability availability)
{
switch (availability) {
case LiteWalletGatewayAvailability::Ready: return "Ready";
case LiteWalletGatewayAvailability::UnsupportedBuild: return "UnsupportedBuild";
case LiteWalletGatewayAvailability::BackendUnavailable: return "BackendUnavailable";
case LiteWalletGatewayAvailability::BridgeUnavailable: return "BridgeUnavailable";
case LiteWalletGatewayAvailability::BridgeCallsDisabled: return "BridgeCallsDisabled";
case LiteWalletGatewayAvailability::NoUsableServer: return "NoUsableServer";
}
return "Unknown";
}
std::vector<LiteWalletGatewayCommand> liteWalletRefreshCommands(const LiteWalletRefreshRequest& request)
{
std::vector<LiteWalletGatewayCommand> commands;
if (request.includeInfo) commands.push_back(LiteWalletGatewayCommand::Info);
if (request.includeHeight) commands.push_back(LiteWalletGatewayCommand::Height);
if (request.includeBalance) commands.push_back(LiteWalletGatewayCommand::Balance);
if (request.includeAddresses) commands.push_back(LiteWalletGatewayCommand::Addresses);
if (request.includeNotes) commands.push_back(LiteWalletGatewayCommand::Notes);
if (request.includeTransactions) commands.push_back(LiteWalletGatewayCommand::List);
return commands;
}
LiteWalletRefreshBundle assembleLiteWalletRefreshBundle(const std::vector<LiteWalletGatewayCommandResult>& results,
const LiteWalletRefreshRequest& request)
{
LiteWalletRefreshBundle bundle;
if (request.haveSyncStatus) {
bundle.hasSyncStatus = true;
bundle.syncStatus = request.syncStatus;
}
for (const auto& result : results) {
if (!result.ok) continue;
++bundle.successfulCommandCount;
switch (result.plan.command) {
case LiteWalletGatewayCommand::Info:
bundle.hasInfo = true;
bundle.info = result.info;
break;
case LiteWalletGatewayCommand::Height:
bundle.hasHeight = true;
bundle.height = result.height;
break;
case LiteWalletGatewayCommand::Balance:
bundle.hasBalance = true;
bundle.balance = result.balance;
break;
case LiteWalletGatewayCommand::Addresses:
bundle.hasAddresses = true;
bundle.addresses = result.addresses;
break;
case LiteWalletGatewayCommand::Notes:
bundle.hasNotes = true;
bundle.notes = result.notes;
break;
case LiteWalletGatewayCommand::List:
bundle.hasTransactions = true;
bundle.transactions = result.transactions;
break;
}
}
const auto expectedCommands = liteWalletRefreshCommands(request);
bundle.complete = bundle.successfulCommandCount == expectedCommands.size();
return bundle;
}
LiteWalletGateway::LiteWalletGateway(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge bridge,
LiteWalletGatewayOptions options)
: capabilities_(capabilities),
connectionSettings_(std::move(connectionSettings)),
bridge_(std::move(bridge)),
options_(options)
{
}
LiteWalletGatewayAvailability LiteWalletGateway::availability() const
{
if (!isLiteBuild(capabilities_)) return LiteWalletGatewayAvailability::UnsupportedBuild;
if (!supportsLiteBackend(capabilities_)) return LiteWalletGatewayAvailability::BackendUnavailable;
if (!bridge_.available()) return LiteWalletGatewayAvailability::BridgeUnavailable;
if (!selectLiteServer(connectionSettings_).ok) return LiteWalletGatewayAvailability::NoUsableServer;
if (!options_.allowBridgeCalls) return LiteWalletGatewayAvailability::BridgeCallsDisabled;
return LiteWalletGatewayAvailability::Ready;
}
WalletBackendStatus LiteWalletGateway::status() const
{
const auto currentAvailability = availability();
if (currentAvailability == LiteWalletGatewayAvailability::NoUsableServer) {
return statusFor(currentAvailability, selectLiteServer(connectionSettings_).error);
}
return statusFor(currentAvailability);
}
LiteWalletGatewayPlan LiteWalletGateway::planCommand(const LiteWalletGatewayRequest& request) const
{
LiteWalletGatewayPlan plan;
plan.command = request.command;
plan.commandName = liteWalletGatewayCommandName(request.command);
plan.bridgeExecutionAllowed = options_.allowBridgeCalls;
auto selection = selectServerForRequest(request.serverUrl);
if (!selection.ok) {
plan.error = selection.error;
return plan;
}
plan.ok = true;
plan.server = selection.server;
plan.serverIndex = selection.serverIndex;
plan.customServer = selection.customServer;
return plan;
}
LiteWalletRefreshPlan LiteWalletGateway::planRefresh(const LiteWalletRefreshRequest& request) const
{
LiteWalletRefreshPlan plan;
const auto commands = liteWalletRefreshCommands(request);
if (commands.empty()) {
plan.error = "lite refresh request has no commands";
return plan;
}
for (const auto command : commands) {
const auto commandPlan = planCommand(LiteWalletGatewayRequest{command, request.serverUrl});
if (!commandPlan.ok) {
plan.error = commandPlan.error;
return plan;
}
plan.commands.push_back(commandPlan);
}
plan.ok = true;
return plan;
}
LiteWalletGatewayCommandResult LiteWalletGateway::fetchCommand(const LiteWalletGatewayRequest& request)
{
const auto plan = planCommand(request);
if (!plan.ok) return blockedCommandResult(plan, makeGatewayStatus(WalletBackendState::Error, plan.error));
if (availability() != LiteWalletGatewayAvailability::Ready) return blockedCommandResult(plan, status());
return executePlannedCommand(plan);
}
LiteWalletRefreshResult LiteWalletGateway::refresh(const LiteWalletRefreshRequest& request)
{
LiteWalletRefreshResult result;
result.plan = planRefresh(request);
if (!result.plan.ok) {
result.status = makeGatewayStatus(WalletBackendState::Error, result.plan.error);
result.error = result.status.message;
return result;
}
if (availability() != LiteWalletGatewayAvailability::Ready) {
result.status = status();
result.error = result.status.message;
return result;
}
result.attempted = true;
for (const auto& commandPlan : result.plan.commands) {
auto commandResult = executePlannedCommand(commandPlan);
if (!commandResult.ok) {
result.status = commandResult.status;
result.error = commandResult.error;
result.commandResults.push_back(std::move(commandResult));
return result;
}
result.commandResults.push_back(std::move(commandResult));
}
result.bundle = assembleLiteWalletRefreshBundle(result.commandResults, request);
result.ok = result.bundle.complete;
result.status = refreshStatusForRequest(request);
if (!result.ok) {
result.status = makeGatewayStatus(WalletBackendState::Error, "lite refresh bundle is incomplete");
result.error = result.status.message;
}
return result;
}
LiteServerSelectionResult LiteWalletGateway::selectServerForRequest(const std::string& serverUrl) const
{
const auto overrideUrl = trimGatewayCopy(serverUrl);
if (!overrideUrl.empty()) {
if (!isLiteServerUrlUsable(overrideUrl)) {
return LiteServerSelectionResult{false, {}, 0, false, "lite gateway server URL is not usable"};
}
return LiteServerSelectionResult{
true,
LiteServerEndpoint{overrideUrl, "Request", true},
0,
true,
{}
};
}
return selectLiteServer(connectionSettings_);
}
WalletBackendStatus LiteWalletGateway::statusFor(LiteWalletGatewayAvailability availability,
const std::string& detail) const
{
switch (availability) {
case LiteWalletGatewayAvailability::Ready:
return makeGatewayStatus(
WalletBackendState::Disconnected,
detail.empty() ? "lite wallet gateway scaffold ready; refresh execution is fake-test only" : detail);
case LiteWalletGatewayAvailability::UnsupportedBuild:
return makeGatewayStatus(
WalletBackendState::Unavailable,
"lite wallet gateway is unsupported in full-node builds");
case LiteWalletGatewayAvailability::BackendUnavailable:
return makeGatewayStatus(
WalletBackendState::Unavailable,
"lite backend is not linked");
case LiteWalletGatewayAvailability::BridgeUnavailable:
return makeGatewayStatus(
WalletBackendState::Unavailable,
detail.empty() ? bridge_.unavailableReason() : detail);
case LiteWalletGatewayAvailability::BridgeCallsDisabled:
return makeGatewayStatus(
WalletBackendState::Unavailable,
"lite wallet gateway bridge calls are disabled");
case LiteWalletGatewayAvailability::NoUsableServer:
return makeGatewayStatus(
WalletBackendState::Error,
detail.empty() ? "no usable lite servers are configured" : detail);
}
return makeGatewayStatus(WalletBackendState::Unavailable, "unknown lite wallet gateway state");
}
LiteWalletGatewayCommandResult LiteWalletGateway::blockedCommandResult(
const LiteWalletGatewayPlan& plan,
const WalletBackendStatus& blockedStatus) const
{
LiteWalletGatewayCommandResult result;
result.plan = plan;
result.status = blockedStatus;
result.error = blockedStatus.message;
return result;
}
LiteWalletGatewayCommandResult LiteWalletGateway::executePlannedCommand(const LiteWalletGatewayPlan& plan)
{
LiteWalletGatewayCommandResult result;
result.plan = plan;
result.attempted = true;
const auto bridgeCall = bridge_.execute(plan.commandName, plan.args);
result.bridgeResponseRedacted = bridgeCall.ok || !bridgeCall.error.empty() ? "<redacted>" : "<empty>";
if (!bridgeCall.ok) {
result.status = makeGatewayStatus(WalletBackendState::Error, "lite wallet gateway bridge call failed");
result.error = result.status.message;
return result;
}
return parseBridgeResponse(plan, bridgeCall.value);
}
LiteWalletGatewayCommandResult LiteWalletGateway::parseBridgeResponse(
const LiteWalletGatewayPlan& plan,
const std::string& response) const
{
auto result = parsedCommandResult(plan);
switch (plan.command) {
case LiteWalletGatewayCommand::Info: {
const auto parsed = parseLiteInfoResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.info = parsed.info;
return result;
}
case LiteWalletGatewayCommand::Height: {
const auto parsed = parseLiteHeightResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.height = parsed.height;
return result;
}
case LiteWalletGatewayCommand::Balance: {
const auto parsed = parseLiteBalanceResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.balance = parsed.balance;
return result;
}
case LiteWalletGatewayCommand::Addresses: {
const auto parsed = parseLiteAddressesResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.addresses = parsed.addresses;
return result;
}
case LiteWalletGatewayCommand::Notes: {
const auto parsed = parseLiteNotesResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.notes = parsed.notes;
return result;
}
case LiteWalletGatewayCommand::List: {
const auto parsed = parseLiteTransactionsResponse(response);
if (!parsed.ok) {
result.ok = false;
result.parserError = parsed.error;
break;
}
result.transactions = parsed.transactions;
return result;
}
}
result.bridgeAccepted = true;
result.status = makeGatewayStatus(WalletBackendState::Error, "lite wallet gateway response could not be parsed");
result.error = result.status.message;
result.bridgeResponseRedacted = "<redacted>";
return result;
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,163 @@
#pragma once
#include "lite_client_bridge.h"
#include "lite_connection_service.h"
#include "lite_result_parsers.h"
#include "lite_sync_service.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletGatewayCommand {
Info,
Height,
Balance,
Addresses,
Notes,
List,
};
enum class LiteWalletGatewayAvailability {
Ready,
UnsupportedBuild,
BackendUnavailable,
BridgeUnavailable,
BridgeCallsDisabled,
NoUsableServer,
};
struct LiteWalletGatewayRequest {
LiteWalletGatewayCommand command = LiteWalletGatewayCommand::Info;
std::string serverUrl;
};
struct LiteWalletGatewayPlan {
bool ok = false;
LiteWalletGatewayCommand command = LiteWalletGatewayCommand::Info;
LiteServerEndpoint server;
std::size_t serverIndex = 0;
bool customServer = false;
bool bridgeExecutionAllowed = false;
std::string commandName;
std::string args;
std::string error;
};
struct LiteWalletGatewayCommandResult {
bool ok = false;
bool attempted = false;
bool bridgeAccepted = false;
LiteWalletGatewayPlan plan;
LiteResultParserError parserError = LiteResultParserError::None;
WalletBackendStatus status;
std::string bridgeResponseRedacted = "<empty>";
std::string error;
LiteInfoResponse info;
LiteHeightResponse height;
LiteBalanceResponse balance;
LiteAddressesResponse addresses;
LiteNotesResponse notes;
LiteTransactionsResponse transactions;
};
struct LiteWalletRefreshRequest {
std::string serverUrl;
bool includeInfo = true;
bool includeHeight = true;
bool includeBalance = true;
bool includeAddresses = true;
bool includeNotes = true;
bool includeTransactions = true;
bool haveSyncStatus = false;
LiteSyncStatusResponse syncStatus;
};
struct LiteWalletRefreshPlan {
bool ok = false;
std::vector<LiteWalletGatewayPlan> commands;
std::string error;
};
struct LiteWalletRefreshBundle {
bool hasInfo = false;
bool hasHeight = false;
bool hasBalance = false;
bool hasAddresses = false;
bool hasNotes = false;
bool hasTransactions = false;
bool hasSyncStatus = false;
bool complete = false;
std::size_t successfulCommandCount = 0;
LiteInfoResponse info;
LiteHeightResponse height;
LiteBalanceResponse balance;
LiteAddressesResponse addresses;
LiteNotesResponse notes;
LiteTransactionsResponse transactions;
LiteSyncStatusResponse syncStatus;
};
struct LiteWalletRefreshResult {
bool ok = false;
bool attempted = false;
LiteWalletRefreshPlan plan;
std::vector<LiteWalletGatewayCommandResult> commandResults;
LiteWalletRefreshBundle bundle;
WalletBackendStatus status;
std::string error;
};
struct LiteWalletGatewayOptions {
bool allowBridgeCalls = false;
};
const char* liteWalletGatewayCommandName(LiteWalletGatewayCommand command);
const char* liteWalletGatewayAvailabilityName(LiteWalletGatewayAvailability availability);
std::vector<LiteWalletGatewayCommand> liteWalletRefreshCommands(const LiteWalletRefreshRequest& request);
LiteWalletRefreshBundle assembleLiteWalletRefreshBundle(const std::vector<LiteWalletGatewayCommandResult>& results,
const LiteWalletRefreshRequest& request);
class LiteWalletGateway {
public:
LiteWalletGateway(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge bridge,
LiteWalletGatewayOptions options = {});
const WalletCapabilities& capabilities() const { return capabilities_; }
const LiteConnectionSettings& connectionSettings() const { return connectionSettings_; }
const LiteWalletGatewayOptions& options() const { return options_; }
LiteWalletGatewayAvailability availability() const;
WalletBackendStatus status() const;
LiteWalletGatewayPlan planCommand(const LiteWalletGatewayRequest& request) const;
LiteWalletRefreshPlan planRefresh(const LiteWalletRefreshRequest& request) const;
LiteWalletGatewayCommandResult fetchCommand(const LiteWalletGatewayRequest& request);
LiteWalletRefreshResult refresh(const LiteWalletRefreshRequest& request);
private:
LiteServerSelectionResult selectServerForRequest(const std::string& serverUrl) const;
WalletBackendStatus statusFor(LiteWalletGatewayAvailability availability,
const std::string& detail = {}) const;
LiteWalletGatewayCommandResult blockedCommandResult(const LiteWalletGatewayPlan& plan,
const WalletBackendStatus& blockedStatus) const;
LiteWalletGatewayCommandResult executePlannedCommand(const LiteWalletGatewayPlan& plan);
LiteWalletGatewayCommandResult parseBridgeResponse(const LiteWalletGatewayPlan& plan,
const std::string& response) const;
WalletCapabilities capabilities_;
LiteConnectionSettings connectionSettings_;
LiteClientBridge bridge_;
LiteWalletGatewayOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,397 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "lite_wallet_lifecycle_service.h"
#include <cctype>
#include <utility>
#include <nlohmann/json.hpp>
namespace dragonx {
namespace wallet {
namespace {
std::string trimLifecycleCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
LiteRedactedPrivateData redactedField(LitePrivateDataKind kind, const std::string& value)
{
return LiteRedactedPrivateData{kind, !value.empty(), redactLitePrivateDataValue(value)};
}
std::vector<LiteRedactedPrivateData> createPrivateData(const LiteWalletCreateRequest& request)
{
return {redactedField(LitePrivateDataKind::Passphrase, request.passphrase)};
}
std::vector<LiteRedactedPrivateData> openPrivateData(const LiteWalletOpenRequest& request)
{
return {
redactedField(LitePrivateDataKind::WalletPath, request.walletPath),
redactedField(LitePrivateDataKind::Passphrase, request.passphrase)
};
}
std::vector<LiteRedactedPrivateData> restorePrivateData(const LiteWalletRestoreRequest& request)
{
return {
redactedField(LitePrivateDataKind::SeedPhrase, request.seedPhrase),
redactedField(LitePrivateDataKind::WalletPath, request.walletPath),
redactedField(LitePrivateDataKind::Passphrase, request.passphrase)
};
}
WalletBackendStatus lifecycleCompletedStatus()
{
return WalletBackendStatus{
WalletBackendState::Disconnected,
"lite wallet lifecycle bridge call completed; sync is not implemented",
{},
{},
0.0
};
}
} // namespace
const char* liteWalletLifecycleOperationName(LiteWalletLifecycleOperation operation)
{
switch (operation) {
case LiteWalletLifecycleOperation::CreateNew:
return "CreateNew";
case LiteWalletLifecycleOperation::OpenExisting:
return "OpenExisting";
case LiteWalletLifecycleOperation::RestoreFromSeed:
return "RestoreFromSeed";
}
return "Unknown";
}
const char* liteWalletLifecycleAvailabilityName(LiteWalletLifecycleAvailability availability)
{
switch (availability) {
case LiteWalletLifecycleAvailability::Ready:
return "Ready";
case LiteWalletLifecycleAvailability::UnsupportedBuild:
return "UnsupportedBuild";
case LiteWalletLifecycleAvailability::BackendUnavailable:
return "BackendUnavailable";
case LiteWalletLifecycleAvailability::BridgeUnavailable:
return "BridgeUnavailable";
case LiteWalletLifecycleAvailability::BridgeCallsDisabled:
return "BridgeCallsDisabled";
case LiteWalletLifecycleAvailability::NoUsableServer:
return "NoUsableServer";
}
return "Unknown";
}
const char* litePrivateDataKindName(LitePrivateDataKind kind)
{
switch (kind) {
case LitePrivateDataKind::SeedPhrase:
return "SeedPhrase";
case LitePrivateDataKind::Passphrase:
return "Passphrase";
case LitePrivateDataKind::WalletPath:
return "WalletPath";
case LitePrivateDataKind::BridgeResponse:
return "BridgeResponse";
}
return "Unknown";
}
std::string redactLitePrivateDataValue(const std::string& value)
{
return value.empty() ? "<empty>" : "<redacted>";
}
LiteWalletLifecycleService::LiteWalletLifecycleService(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge bridge,
LiteWalletLifecycleOptions options)
: capabilities_(capabilities),
connectionSettings_(std::move(connectionSettings)),
bridge_(std::move(bridge)),
options_(options)
{
}
LiteWalletLifecycleAvailability LiteWalletLifecycleService::availability() const
{
if (!isLiteBuild(capabilities_)) return LiteWalletLifecycleAvailability::UnsupportedBuild;
if (!supportsLiteBackend(capabilities_)) return LiteWalletLifecycleAvailability::BackendUnavailable;
if (!bridge_.available()) return LiteWalletLifecycleAvailability::BridgeUnavailable;
if (!selectLiteServer(connectionSettings_).ok) return LiteWalletLifecycleAvailability::NoUsableServer;
if (!options_.allowBridgeCalls) return LiteWalletLifecycleAvailability::BridgeCallsDisabled;
return LiteWalletLifecycleAvailability::Ready;
}
WalletBackendStatus LiteWalletLifecycleService::status() const
{
const auto currentAvailability = availability();
if (currentAvailability == LiteWalletLifecycleAvailability::NoUsableServer) {
return statusFor(currentAvailability, selectLiteServer(connectionSettings_).error);
}
return statusFor(currentAvailability);
}
LiteWalletLifecyclePlan LiteWalletLifecycleService::planCreateWallet(
const LiteWalletCreateRequest& request) const
{
return makePlan(LiteWalletLifecycleOperation::CreateNew,
request.serverUrl,
createPrivateData(request));
}
LiteWalletLifecyclePlan LiteWalletLifecycleService::planOpenWallet(
const LiteWalletOpenRequest& request) const
{
return makePlan(LiteWalletLifecycleOperation::OpenExisting,
request.serverUrl,
openPrivateData(request));
}
LiteWalletLifecyclePlan LiteWalletLifecycleService::planRestoreWallet(
const LiteWalletRestoreRequest& request) const
{
const std::string validationError = trimLifecycleCopy(request.seedPhrase).empty()
? "restore seed phrase is required"
: std::string();
return makePlan(LiteWalletLifecycleOperation::RestoreFromSeed,
request.serverUrl,
restorePrivateData(request),
validationError);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::createWallet(
const LiteWalletCreateRequest& request)
{
const auto plan = planCreateWallet(request);
if (!plan.ok) return blockedResult(plan, WalletBackendStatus{WalletBackendState::Error, plan.error, {}, {}, 0.0});
const auto currentStatus = status();
if (availability() != LiteWalletLifecycleAvailability::Ready) return blockedResult(plan, currentStatus);
return executeCreate(request, plan);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::openWallet(
const LiteWalletOpenRequest& request)
{
const auto plan = planOpenWallet(request);
if (!plan.ok) return blockedResult(plan, WalletBackendStatus{WalletBackendState::Error, plan.error, {}, {}, 0.0});
const auto currentStatus = status();
if (availability() != LiteWalletLifecycleAvailability::Ready) return blockedResult(plan, currentStatus);
return executeOpen(request, plan);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::restoreWallet(
const LiteWalletRestoreRequest& request)
{
const auto plan = planRestoreWallet(request);
if (!plan.ok) return blockedResult(plan, WalletBackendStatus{WalletBackendState::Error, plan.error, {}, {}, 0.0});
const auto currentStatus = status();
if (availability() != LiteWalletLifecycleAvailability::Ready) return blockedResult(plan, currentStatus);
return executeRestore(request, plan);
}
LiteServerSelectionResult LiteWalletLifecycleService::selectServerForRequest(
const std::string& serverUrl) const
{
const std::string overrideUrl = trimLifecycleCopy(serverUrl);
if (!overrideUrl.empty()) {
if (!isLiteServerUrlUsable(overrideUrl)) {
return LiteServerSelectionResult{false, {}, 0, false, "lite lifecycle server URL is not usable"};
}
return LiteServerSelectionResult{
true,
LiteServerEndpoint{overrideUrl, "Request", true},
0,
true,
{}
};
}
return selectLiteServer(connectionSettings_);
}
LiteWalletLifecyclePlan LiteWalletLifecycleService::makePlan(
LiteWalletLifecycleOperation operation,
const std::string& serverUrl,
std::vector<LiteRedactedPrivateData> privateData,
const std::string& validationError) const
{
LiteWalletLifecyclePlan plan;
plan.operation = operation;
plan.privateData = std::move(privateData);
plan.bridgeExecutionAllowed = options_.allowBridgeCalls;
if (!validationError.empty()) {
plan.error = validationError;
return plan;
}
auto selection = selectServerForRequest(serverUrl);
if (!selection.ok) {
plan.error = selection.error;
return plan;
}
plan.ok = true;
plan.server = selection.server;
plan.serverIndex = selection.serverIndex;
plan.customServer = selection.customServer;
return plan;
}
WalletBackendStatus LiteWalletLifecycleService::statusFor(
LiteWalletLifecycleAvailability availability,
const std::string& detail) const
{
switch (availability) {
case LiteWalletLifecycleAvailability::Ready:
return WalletBackendStatus{
WalletBackendState::Disconnected,
detail.empty() ? "lite wallet lifecycle scaffold ready; sync is not implemented" : detail,
{},
{},
0.0
};
case LiteWalletLifecycleAvailability::UnsupportedBuild:
return WalletBackendStatus{
WalletBackendState::Unavailable,
"lite wallet lifecycle is unsupported in full-node builds",
{},
{},
0.0
};
case LiteWalletLifecycleAvailability::BackendUnavailable:
return WalletBackendStatus{
WalletBackendState::Unavailable,
"lite backend is not linked",
{},
{},
0.0
};
case LiteWalletLifecycleAvailability::BridgeUnavailable:
return WalletBackendStatus{
WalletBackendState::Unavailable,
detail.empty() ? bridge_.unavailableReason() : detail,
{},
{},
0.0
};
case LiteWalletLifecycleAvailability::BridgeCallsDisabled:
return WalletBackendStatus{
WalletBackendState::Unavailable,
"lite wallet lifecycle bridge calls are disabled",
{},
{},
0.0
};
case LiteWalletLifecycleAvailability::NoUsableServer:
return WalletBackendStatus{
WalletBackendState::Error,
detail.empty() ? "no usable lite servers are configured" : detail,
{},
{},
0.0
};
}
return WalletBackendStatus{WalletBackendState::Unavailable, "unknown lite wallet lifecycle state", {}, {}, 0.0};
}
LiteWalletLifecycleResult LiteWalletLifecycleService::executeCreate(
const LiteWalletCreateRequest& request,
const LiteWalletLifecyclePlan& plan)
{
auto bridgeCall = bridge_.initializeNew(request.dangerous, plan.server.url);
return bridgeResult(plan, lifecycleCompletedStatus(), bridgeCall);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::executeOpen(
const LiteWalletOpenRequest& request,
const LiteWalletLifecyclePlan& plan)
{
auto bridgeCall = bridge_.initializeExisting(request.dangerous, plan.server.url);
return bridgeResult(plan, lifecycleCompletedStatus(), bridgeCall);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::executeRestore(
const LiteWalletRestoreRequest& request,
const LiteWalletLifecyclePlan& plan)
{
auto bridgeCall = bridge_.initializeNewFromPhrase(
request.dangerous,
plan.server.url,
request.seedPhrase,
request.birthday,
request.account,
request.overwrite);
return bridgeResult(plan, lifecycleCompletedStatus(), bridgeCall);
}
LiteWalletLifecycleResult LiteWalletLifecycleService::blockedResult(
const LiteWalletLifecyclePlan& plan,
const WalletBackendStatus& blockedStatus) const
{
LiteWalletLifecycleResult result;
result.operation = plan.operation;
result.plan = plan;
result.status = blockedStatus;
result.error = blockedStatus.message;
return result;
}
LiteWalletLifecycleResult LiteWalletLifecycleService::bridgeResult(
const LiteWalletLifecyclePlan& plan,
const WalletBackendStatus& successStatus,
const LiteBridgeStringResult& bridgeCall) const
{
LiteWalletLifecycleResult result;
result.operation = plan.operation;
result.plan = plan;
result.attempted = true;
result.bridgeResponseRedacted = redactLitePrivateDataValue(bridgeCall.ok ? bridgeCall.value : bridgeCall.error);
if (!bridgeCall.ok) {
result.status = WalletBackendStatus{
WalletBackendState::Error,
"lite wallet lifecycle bridge call failed",
{},
{},
0.0
};
result.error = result.status.message;
return result;
}
result.ok = true;
result.bridgeAccepted = true;
// The bridge already classifies "Error:"-prefixed responses as failures (ok=false).
// A successful init returns a JSON document (seed info for create, wallet info for
// open/restore); treat a well-formed JSON response as a genuinely ready wallet rather
// than discarding it. (Richer field extraction lands with the sync slice.)
result.walletReady = nlohmann::json::accept(bridgeCall.value);
result.status = successStatus;
return result;
}
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,152 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "lite_client_bridge.h"
#include "lite_connection_service.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx {
namespace wallet {
enum class LiteWalletLifecycleOperation {
CreateNew,
OpenExisting,
RestoreFromSeed
};
enum class LiteWalletLifecycleAvailability {
Ready,
UnsupportedBuild,
BackendUnavailable,
BridgeUnavailable,
BridgeCallsDisabled,
NoUsableServer
};
enum class LitePrivateDataKind {
SeedPhrase,
Passphrase,
WalletPath,
BridgeResponse
};
struct LiteRedactedPrivateData {
LitePrivateDataKind kind = LitePrivateDataKind::SeedPhrase;
bool present = false;
std::string redactedValue = "<empty>";
};
struct LiteWalletCreateRequest {
bool dangerous = false;
std::string serverUrl;
std::string passphrase;
};
struct LiteWalletOpenRequest {
bool dangerous = false;
std::string serverUrl;
std::string walletPath;
std::string passphrase;
};
struct LiteWalletRestoreRequest {
bool dangerous = false;
std::string serverUrl;
std::string seedPhrase;
unsigned long long birthday = 0;
unsigned long long account = 0;
bool overwrite = false;
std::string walletPath;
std::string passphrase;
};
struct LiteWalletLifecyclePlan {
bool ok = false;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteServerEndpoint server;
std::size_t serverIndex = 0;
bool customServer = false;
bool bridgeExecutionAllowed = false;
std::vector<LiteRedactedPrivateData> privateData;
std::string error;
};
struct LiteWalletLifecycleResult {
bool ok = false;
bool attempted = false;
bool bridgeAccepted = false;
bool walletReady = false;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteWalletLifecyclePlan plan;
WalletBackendStatus status;
std::string bridgeResponseRedacted = "<empty>";
std::string error;
};
struct LiteWalletLifecycleOptions {
bool allowBridgeCalls = false;
};
const char* liteWalletLifecycleOperationName(LiteWalletLifecycleOperation operation);
const char* liteWalletLifecycleAvailabilityName(LiteWalletLifecycleAvailability availability);
const char* litePrivateDataKindName(LitePrivateDataKind kind);
std::string redactLitePrivateDataValue(const std::string& value);
class LiteWalletLifecycleService {
public:
LiteWalletLifecycleService(WalletCapabilities capabilities,
LiteConnectionSettings connectionSettings,
LiteClientBridge bridge,
LiteWalletLifecycleOptions options = {});
const LiteConnectionSettings& connectionSettings() const { return connectionSettings_; }
const WalletCapabilities& capabilities() const { return capabilities_; }
const LiteWalletLifecycleOptions& options() const { return options_; }
LiteWalletLifecycleAvailability availability() const;
WalletBackendStatus status() const;
LiteWalletLifecyclePlan planCreateWallet(const LiteWalletCreateRequest& request) const;
LiteWalletLifecyclePlan planOpenWallet(const LiteWalletOpenRequest& request) const;
LiteWalletLifecyclePlan planRestoreWallet(const LiteWalletRestoreRequest& request) const;
LiteWalletLifecycleResult createWallet(const LiteWalletCreateRequest& request);
LiteWalletLifecycleResult openWallet(const LiteWalletOpenRequest& request);
LiteWalletLifecycleResult restoreWallet(const LiteWalletRestoreRequest& request);
private:
LiteServerSelectionResult selectServerForRequest(const std::string& serverUrl) const;
LiteWalletLifecyclePlan makePlan(LiteWalletLifecycleOperation operation,
const std::string& serverUrl,
std::vector<LiteRedactedPrivateData> privateData,
const std::string& validationError = {}) const;
WalletBackendStatus statusFor(LiteWalletLifecycleAvailability availability,
const std::string& detail = {}) const;
LiteWalletLifecycleResult executeCreate(const LiteWalletCreateRequest& request,
const LiteWalletLifecyclePlan& plan);
LiteWalletLifecycleResult executeOpen(const LiteWalletOpenRequest& request,
const LiteWalletLifecyclePlan& plan);
LiteWalletLifecycleResult executeRestore(const LiteWalletRestoreRequest& request,
const LiteWalletLifecyclePlan& plan);
LiteWalletLifecycleResult blockedResult(const LiteWalletLifecyclePlan& plan,
const WalletBackendStatus& blockedStatus) const;
LiteWalletLifecycleResult bridgeResult(const LiteWalletLifecyclePlan& plan,
const WalletBackendStatus& successStatus,
const LiteBridgeStringResult& bridgeCall) const;
WalletCapabilities capabilities_;
LiteConnectionSettings connectionSettings_;
LiteClientBridge bridge_;
LiteWalletLifecycleOptions options_;
};
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,440 @@
#include "lite_wallet_lifecycle_ui_adapter.h"
#include <sstream>
#include <utility>
namespace dragonx {
namespace wallet {
namespace {
void addIssue(std::vector<LiteWalletLifecycleUiExecutionIssueInfo>& issues,
LiteWalletLifecycleUiExecutionIssue issue,
std::string message)
{
issues.push_back(LiteWalletLifecycleUiExecutionIssueInfo{issue, std::move(message)});
}
LiteWalletLifecycleUiExecutionResult stoppedResult(
LiteWalletLifecycleUiExecutionResult result,
LiteWalletLifecycleUiExecutionStatus status,
LiteWalletLifecycleUiExecutionIssue issue,
const std::string& message)
{
result.status = status;
result.error = message;
result.ok = false;
addIssue(result.issues, issue, message);
return result;
}
LiteWalletLifecycleUiExecutionStatus statusFromServerSelection(
LiteWalletServerSelectionUiExecutionStatus status)
{
switch (status) {
case LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle:
return LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild:
return LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability:
return LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings:
return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection:
return LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner:
return LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus:
return LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi:
return LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction:
return LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction;
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed:
return LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed;
case LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed:
return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings;
case LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled:
return LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled;
}
return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings;
}
LiteWalletLifecycleUiExecutionStatus statusFromLifecycle(
LiteWalletServerLifecycleReadinessStatus status)
{
switch (status) {
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle:
return LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild:
return LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild;
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability:
return LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability;
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection:
return LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent:
return LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection;
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus:
return LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi:
return LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction:
return LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction;
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed:
return LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed;
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled:
return LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled;
}
return LiteWalletLifecycleUiExecutionStatus::WaitingForSettings;
}
LiteWalletLifecycleUiExecutionIssue issueFromLifecycle(
LiteWalletServerLifecycleReadinessIssue issue)
{
switch (issue) {
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild:
return LiteWalletLifecycleUiExecutionIssue::FullNodeBuild;
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing:
return LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing;
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded:
return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing:
return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing;
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing:
return LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing;
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing:
return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing;
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing:
return LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing;
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing:
return LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing;
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed:
return LiteWalletLifecycleUiExecutionIssue::LifecycleOperationUnconfirmed;
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing:
return LiteWalletLifecycleUiExecutionIssue::OpenWalletPathMissing;
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing:
return LiteWalletLifecycleUiExecutionIssue::RestoreSeedMissing;
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing:
return LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing;
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing:
return LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing;
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested:
return LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested;
}
return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
}
LiteWalletLifecycleUiExecutionIssue issueFromServerSelection(
LiteWalletServerSelectionUiExecutionIssue issue)
{
switch (issue) {
case LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild:
return LiteWalletLifecycleUiExecutionIssue::FullNodeBuild;
case LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing:
return LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing;
case LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded:
return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
case LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing:
return LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing;
case LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing:
return LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing;
case LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing:
return LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing;
case LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing:
return LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing;
case LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing:
return LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing;
case LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing:
return LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing;
case LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed:
return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
case LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested:
return LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested;
case LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested:
return LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested;
case LiteWalletServerSelectionUiExecutionIssue::SyncRequested:
return LiteWalletLifecycleUiExecutionIssue::SyncRequested;
case LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested:
return LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested;
case LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested:
return LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested;
case LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested:
return LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested;
case LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested:
return LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested;
case LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested:
return LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested;
}
return LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
}
void copyServerSelectionIssues(LiteWalletLifecycleUiExecutionResult& result)
{
for (const auto& issue : result.serverSelectionReport.issues) {
addIssue(result.issues, issueFromServerSelection(issue.issue), issue.message);
}
}
void copyLifecycleIssues(LiteWalletLifecycleUiExecutionResult& result)
{
for (const auto& issue : result.lifecycleReadiness.issues) {
addIssue(result.issues, issueFromLifecycle(issue.issue), issue.message);
}
}
LiteWalletServerSelectionUiExecutionInput makeServerSelectionInput(
const LiteWalletLifecycleUiExecutionInput& input,
const config::Settings& settings)
{
LiteWalletServerSelectionUiExecutionInput selection;
selection.capabilities = input.capabilities;
selection.persistence.settingsLoaded = input.settingsLoaded;
selection.persistence.havePersistedSelectionIntent =
!input.requirePersistedServerSelectionIntent || settings.getLitePersistSelectedServer();
selection.persistence.persistSelectedServer = false;
selection.persistence.persistenceOwnerReady = true;
selection.persistence.writeSettings = false;
selection.ui = input.ui;
selection.operation = input.request.operation;
selection.createRequest = input.request.createRequest;
selection.openRequest = input.request.openRequest;
selection.restoreRequest = input.request.restoreRequest;
selection.requireLifecycleReadiness = true;
return selection;
}
std::string buildDiagnosticSummary(const LiteWalletLifecycleUiExecutionResult& result)
{
std::ostringstream out;
out << "operation=" << liteWalletLifecycleOperationName(result.lifecycleInput.operation)
<< ";server=" << result.selectedServerUrlRedacted
<< ";request=" << result.requestSummaryRedacted
<< ";network=false;bridge=false;lifecycle=false;sync=false;wallet_state=false;wallet_file=false";
return out.str();
}
} // namespace
const char* liteWalletLifecycleUiExecutionStatusName(
LiteWalletLifecycleUiExecutionStatus status)
{
switch (status) {
case LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest:
return "ReadyForFutureLifecycleRequest";
case LiteWalletLifecycleUiExecutionStatus::WaitingForLiteBuild:
return "WaitingForLiteBuild";
case LiteWalletLifecycleUiExecutionStatus::WaitingForBackendCapability:
return "WaitingForBackendCapability";
case LiteWalletLifecycleUiExecutionStatus::WaitingForSettings:
return "WaitingForSettings";
case LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection:
return "WaitingForPersistedServerSelection";
case LiteWalletLifecycleUiExecutionStatus::WaitingForServerSelection:
return "WaitingForServerSelection";
case LiteWalletLifecycleUiExecutionStatus::WaitingForDisplayStatus:
return "WaitingForDisplayStatus";
case LiteWalletLifecycleUiExecutionStatus::WaitingForLifecycleUi:
return "WaitingForLifecycleUi";
case LiteWalletLifecycleUiExecutionStatus::WaitingForRequest:
return "WaitingForRequest";
case LiteWalletLifecycleUiExecutionStatus::WaitingForPrivateDataRedaction:
return "WaitingForPrivateDataRedaction";
case LiteWalletLifecycleUiExecutionStatus::WaitingForSyncPlannerFeed:
return "WaitingForSyncPlannerFeed";
case LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled:
return "RuntimeExecutionDisabled";
}
return "WaitingForSettings";
}
const char* liteWalletLifecycleUiExecutionIssueName(
LiteWalletLifecycleUiExecutionIssue issue)
{
switch (issue) {
case LiteWalletLifecycleUiExecutionIssue::FullNodeBuild:
return "FullNodeBuild";
case LiteWalletLifecycleUiExecutionIssue::LiteBackendCapabilityMissing:
return "LiteBackendCapabilityMissing";
case LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded:
return "SettingsNotLoaded";
case LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing:
return "PersistedServerSelectionIntentMissing";
case LiteWalletLifecycleUiExecutionIssue::ServerSelectionMissing:
return "ServerSelectionMissing";
case LiteWalletLifecycleUiExecutionIssue::SelectedServerDisplayMissing:
return "SelectedServerDisplayMissing";
case LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing:
return "LifecycleUiOwnerMissing";
case LiteWalletLifecycleUiExecutionIssue::LifecycleOperationUnconfirmed:
return "LifecycleOperationUnconfirmed";
case LiteWalletLifecycleUiExecutionIssue::OpenWalletPathMissing:
return "OpenWalletPathMissing";
case LiteWalletLifecycleUiExecutionIssue::RestoreSeedMissing:
return "RestoreSeedMissing";
case LiteWalletLifecycleUiExecutionIssue::PrivateDataRedactionMissing:
return "PrivateDataRedactionMissing";
case LiteWalletLifecycleUiExecutionIssue::SyncPlannerFeedMissing:
return "SyncPlannerFeedMissing";
case LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested:
return "ServerConnectivityCheckRequested";
case LiteWalletLifecycleUiExecutionIssue::WalletExistsCheckRequested:
return "WalletExistsCheckRequested";
case LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested:
return "WalletLifecycleExecutionRequested";
case LiteWalletLifecycleUiExecutionIssue::SyncRequested:
return "SyncRequested";
case LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested:
return "SyncStatusPollingRequested";
case LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested:
return "WorkerQueueRequested";
case LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested:
return "WalletStateMutationRequested";
case LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested:
return "WalletFilePersistenceRequested";
case LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested:
return "SendImportExportRequested";
case LiteWalletLifecycleUiExecutionIssue::SettingsWriteRequested:
return "SettingsWriteRequested";
}
return "SettingsNotLoaded";
}
LiteWalletLifecycleUiExecutionResult executeLiteWalletLifecycleUiRequest(
config::Settings& settings,
const LiteWalletLifecycleUiExecutionInput& input)
{
LiteWalletLifecycleUiExecutionResult result;
auto stopForRuntime = [&](LiteWalletLifecycleUiExecutionIssue issue,
const std::string& message) {
return stoppedResult(std::move(result),
LiteWalletLifecycleUiExecutionStatus::RuntimeExecutionDisabled,
issue,
message);
};
if (input.settingsWriteRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SettingsWriteRequested,
"lite lifecycle request adapter cannot write settings");
}
if (input.serverConnectivityCheckRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::ServerConnectivityCheckRequested,
"lite lifecycle request adapter cannot check server connectivity");
}
if (input.walletExistsCheckRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletExistsCheckRequested,
"lite lifecycle request adapter cannot check wallet existence");
}
if (input.walletLifecycleExecutionRequested || input.ui.realLifecycleExecutionRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletLifecycleExecutionRequested,
"lite lifecycle request adapter cannot execute wallet lifecycle actions");
}
if (input.syncStartRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SyncRequested,
"lite lifecycle request adapter cannot start sync");
}
if (input.syncStatusPollingRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SyncStatusPollingRequested,
"lite lifecycle request adapter cannot poll syncstatus");
}
if (input.workerQueueEnqueueRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WorkerQueueRequested,
"lite lifecycle request adapter cannot enqueue wallet workers");
}
if (input.walletStateMutationRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletStateMutationRequested,
"lite lifecycle request adapter cannot mutate WalletState");
}
if (input.walletFilePersistenceRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::WalletFilePersistenceRequested,
"lite lifecycle request adapter cannot persist wallet files");
}
if (input.sendImportExportExecutionRequested) {
return stopForRuntime(LiteWalletLifecycleUiExecutionIssue::SendImportExportRequested,
"lite lifecycle request adapter cannot execute send/import/export flows");
}
if (!input.settingsLoaded) {
return stoppedResult(std::move(result),
LiteWalletLifecycleUiExecutionStatus::WaitingForSettings,
LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded,
"lite lifecycle settings are not loaded");
}
if (!input.request.requestProvided) {
return stoppedResult(std::move(result),
LiteWalletLifecycleUiExecutionStatus::WaitingForRequest,
LiteWalletLifecycleUiExecutionIssue::LifecycleUiOwnerMissing,
"lite lifecycle request intent is missing");
}
if (input.requirePersistedServerSelectionIntent && !settings.getLitePersistSelectedServer()) {
return stoppedResult(std::move(result),
LiteWalletLifecycleUiExecutionStatus::WaitingForPersistedServerSelection,
LiteWalletLifecycleUiExecutionIssue::PersistedServerSelectionIntentMissing,
"lite lifecycle requires persisted selected-server intent");
}
result.connectionSettings = liteConnectionSettingsFromAppSettings(settings);
result.selectedServerSettingsLoaded = true;
LiteWalletServerSelectionUiExecutionInput selectionInput = makeServerSelectionInput(input, settings);
result.serverSelectionReport = executeLiteWalletServerSelectionUi(settings, selectionInput);
result.connectionSettings = result.serverSelectionReport.connectionSettings;
result.selectedServer = result.serverSelectionReport.selectedServer;
result.selectedServerUrlRedacted = result.serverSelectionReport.selectedServerUrlRedacted;
result.selectedServerIntentAccepted = result.serverSelectionReport.selectedServerIntentAccepted;
result.status = statusFromServerSelection(result.serverSelectionReport.status);
result.lifecycleInput = result.serverSelectionReport.lifecycleInput;
result.lifecycleReadiness = result.serverSelectionReport.lifecycleReadiness;
result.lifecycleReadinessAccepted = result.lifecycleReadiness.ok;
result.lifecycleReportCouldFeedSyncPlanners =
result.lifecycleReadiness.lifecycleReportCouldFeedSyncPlanners;
result.requestAccepted = result.lifecycleReadiness.requestAccepted;
result.privateDataRedacted = result.lifecycleReadiness.requestPlan.privateInputsRedacted;
result.privateData = result.lifecycleReadiness.requestPlan.privateData;
result.requestSummaryRedacted = result.lifecycleReadiness.requestPlan.requestSummary;
result.diagnosticSummary = buildDiagnosticSummary(result);
copyServerSelectionIssues(result);
copyLifecycleIssues(result);
const bool lifecycleReadinessEvaluated = result.lifecycleReadiness.ok ||
!result.lifecycleReadiness.issues.empty() ||
!result.lifecycleReadiness.error.empty() ||
result.lifecycleReadiness.serverSelectionAccepted ||
result.lifecycleReadiness.requestAccepted;
if (!result.serverSelectionReport.ok && !lifecycleReadinessEvaluated) {
result.ok = false;
result.status = statusFromServerSelection(result.serverSelectionReport.status);
result.error = result.serverSelectionReport.error;
return result;
}
if (!result.lifecycleReadiness.ok) {
result.ok = false;
result.status = statusFromLifecycle(result.lifecycleReadiness.status);
result.error = result.lifecycleReadiness.error.empty()
? result.serverSelectionReport.error
: result.lifecycleReadiness.error;
return result;
}
result.ok = true;
result.status = LiteWalletLifecycleUiExecutionStatus::ReadyForFutureLifecycleRequest;
return result;
}
LiteWalletLifecycleUiExecutionAdapter::LiteWalletLifecycleUiExecutionAdapter(
config::Settings& settings)
: settings_(settings)
{
}
LiteWalletLifecycleUiExecutionResult LiteWalletLifecycleUiExecutionAdapter::execute(
const LiteWalletLifecycleUiExecutionInput& input) const
{
return executeLiteWalletLifecycleUiRequest(settings_, input);
}
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,146 @@
#pragma once
#include "lite_wallet_server_selection_adapter.h"
#include "../config/settings.h"
#include <string>
#include <vector>
namespace dragonx {
namespace wallet {
enum class LiteWalletLifecycleUiExecutionStatus {
ReadyForFutureLifecycleRequest,
WaitingForLiteBuild,
WaitingForBackendCapability,
WaitingForSettings,
WaitingForPersistedServerSelection,
WaitingForServerSelection,
WaitingForDisplayStatus,
WaitingForLifecycleUi,
WaitingForRequest,
WaitingForPrivateDataRedaction,
WaitingForSyncPlannerFeed,
RuntimeExecutionDisabled
};
enum class LiteWalletLifecycleUiExecutionIssue {
FullNodeBuild,
LiteBackendCapabilityMissing,
SettingsNotLoaded,
PersistedServerSelectionIntentMissing,
ServerSelectionMissing,
SelectedServerDisplayMissing,
LifecycleUiOwnerMissing,
LifecycleOperationUnconfirmed,
OpenWalletPathMissing,
RestoreSeedMissing,
PrivateDataRedactionMissing,
SyncPlannerFeedMissing,
ServerConnectivityCheckRequested,
WalletExistsCheckRequested,
WalletLifecycleExecutionRequested,
SyncRequested,
SyncStatusPollingRequested,
WorkerQueueRequested,
WalletStateMutationRequested,
WalletFilePersistenceRequested,
SendImportExportRequested,
SettingsWriteRequested
};
struct LiteWalletLifecycleUiRequestIntent {
bool requestProvided = false;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteWalletCreateRequest createRequest;
LiteWalletOpenRequest openRequest;
LiteWalletRestoreRequest restoreRequest;
};
struct LiteWalletLifecycleUiExecutionInput {
WalletCapabilities capabilities;
LiteWalletLifecycleUiRequestIntent request;
bool settingsLoaded = true;
bool requirePersistedServerSelectionIntent = true;
LiteWalletLifecycleUiPrerequisites ui;
bool settingsWriteRequested = false;
bool serverConnectivityCheckRequested = false;
bool walletExistsCheckRequested = false;
bool walletLifecycleExecutionRequested = false;
bool syncStartRequested = false;
bool syncStatusPollingRequested = false;
bool workerQueueEnqueueRequested = false;
bool walletStateMutationRequested = false;
bool walletFilePersistenceRequested = false;
bool sendImportExportExecutionRequested = false;
};
struct LiteWalletLifecycleUiExecutionIssueInfo {
LiteWalletLifecycleUiExecutionIssue issue = LiteWalletLifecycleUiExecutionIssue::SettingsNotLoaded;
std::string message;
};
struct LiteWalletLifecycleUiExecutionResult {
bool ok = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool noBridgeCalls = true;
bool noServerHealthChecked = true;
bool noWalletExistsChecked = true;
bool noWalletCreated = true;
bool noWalletOpened = true;
bool noWalletRestored = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWalletPersistence = true;
bool noSettingsPersistence = true;
bool noSendImportExportExecution = true;
bool settingsWritten = false;
bool workerQueueEnqueued = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool selectedServerSettingsLoaded = false;
bool selectedServerIntentAccepted = false;
bool lifecycleReadinessAccepted = false;
bool requestAccepted = false;
bool privateDataRedacted = false;
bool lifecycleReportCouldFeedSyncPlanners = false;
LiteWalletLifecycleUiExecutionStatus status = LiteWalletLifecycleUiExecutionStatus::WaitingForSettings;
std::string error;
std::string selectedServerUrlRedacted;
std::string requestSummaryRedacted;
std::string diagnosticSummary;
LiteConnectionSettings connectionSettings;
LiteServerSelectionResult selectedServer;
LiteWalletServerSelectionUiExecutionResult serverSelectionReport;
LiteWalletServerLifecycleReadinessInput lifecycleInput;
LiteWalletServerLifecycleReadinessResult lifecycleReadiness;
std::vector<LiteRedactedPrivateData> privateData;
std::vector<LiteWalletLifecycleUiExecutionIssueInfo> issues;
};
const char* liteWalletLifecycleUiExecutionStatusName(
LiteWalletLifecycleUiExecutionStatus status);
const char* liteWalletLifecycleUiExecutionIssueName(
LiteWalletLifecycleUiExecutionIssue issue);
LiteWalletLifecycleUiExecutionResult executeLiteWalletLifecycleUiRequest(
config::Settings& settings,
const LiteWalletLifecycleUiExecutionInput& input);
class LiteWalletLifecycleUiExecutionAdapter {
public:
explicit LiteWalletLifecycleUiExecutionAdapter(config::Settings& settings);
LiteWalletLifecycleUiExecutionResult execute(
const LiteWalletLifecycleUiExecutionInput& input) const;
private:
config::Settings& settings_;
};
} // namespace wallet
} // namespace dragonx

View 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

View File

@@ -0,0 +1,106 @@
#pragma once
#include "lite_wallet_app_refresh_coordinator.h"
#include <cstdint>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletRefreshReadinessStatus {
EligibleDryRun,
CoordinatorRejected,
UnsafeReport,
WaitingForLifecycle,
WaitingForSync,
Stale,
};
enum class LiteWalletRefreshReadinessIssue {
CoordinatorReportRejected,
CoordinatorReportIncomplete,
CoordinatorReportUnsafe,
FullNodeRouteRejected,
LifecycleNotReady,
SyncNotReady,
RefreshTimestampMissing,
RefreshTimestampInFuture,
RefreshStale,
StaleRefreshAllowed,
};
struct LiteWalletRefreshFreshnessInput {
bool haveSnapshotTime = false;
std::uint64_t snapshotTimeSeconds = 0;
std::uint64_t nowSeconds = 0;
};
struct LiteWalletRefreshReadinessInputs {
bool lifecycleReady = false;
WalletBackendStatus lifecycleStatus;
bool syncReady = false;
WalletBackendStatus syncStatus;
LiteWalletRefreshFreshnessInput freshness;
};
struct LiteWalletRefreshReadinessPolicyOptions {
bool requireLifecycleReady = true;
bool requireSyncReady = true;
bool requireFreshRefresh = true;
bool allowStaleRefresh = false;
std::uint64_t maxRefreshAgeSeconds = 120;
};
struct LiteWalletRefreshReadinessIssueInfo {
LiteWalletRefreshReadinessIssue issue = LiteWalletRefreshReadinessIssue::CoordinatorReportRejected;
std::string message;
};
struct LiteWalletRefreshReadinessResult {
bool ok = false;
bool eligibleForFutureUiRefresh = false;
bool coordinatorReportAccepted = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool stateMutationRequested = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool lifecycleReady = false;
bool syncReady = false;
bool refreshFresh = false;
bool refreshStale = false;
bool staleRefreshAllowed = false;
LiteWalletRefreshReadinessStatus status = LiteWalletRefreshReadinessStatus::CoordinatorRejected;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
WalletBackendStatus lifecycleStatus;
WalletBackendStatus syncStatus;
std::uint64_t refreshAgeSeconds = 0;
std::uint64_t maxRefreshAgeSeconds = 0;
std::vector<LiteWalletRefreshReadinessIssueInfo> issues;
std::string error;
};
const char* liteWalletRefreshReadinessStatusName(LiteWalletRefreshReadinessStatus status);
const char* liteWalletRefreshReadinessIssueName(LiteWalletRefreshReadinessIssue issue);
LiteWalletRefreshReadinessResult evaluateLiteWalletRefreshReadiness(
const LiteWalletAppRefreshCoordinationResult& coordination,
const LiteWalletRefreshReadinessInputs& inputs,
LiteWalletRefreshReadinessPolicyOptions options = {});
class LiteWalletRefreshReadinessPolicy {
public:
explicit LiteWalletRefreshReadinessPolicy(LiteWalletRefreshReadinessPolicyOptions options = {});
LiteWalletRefreshReadinessResult evaluate(
const LiteWalletAppRefreshCoordinationResult& coordination,
const LiteWalletRefreshReadinessInputs& inputs) const;
private:
LiteWalletRefreshReadinessPolicyOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,189 @@
#include "wallet/lite_wallet_refresh_service.h"
#include <utility>
namespace dragonx::wallet {
namespace {
WalletBackendStatus makeRefreshStatus(WalletBackendState state, std::string message)
{
WalletBackendStatus status;
status.state = state;
status.message = std::move(message);
return status;
}
} // namespace
LiteWalletGatewayRefreshAdapter::LiteWalletGatewayRefreshAdapter(LiteWalletGateway& gateway)
: gateway_(gateway)
{
}
LiteWalletRefreshPlan LiteWalletGatewayRefreshAdapter::planRefresh(
const LiteWalletRefreshRequest& request) const
{
return gateway_.planRefresh(request);
}
LiteWalletRefreshResult LiteWalletGatewayRefreshAdapter::refresh(
const LiteWalletRefreshRequest& request)
{
return gateway_.refresh(request);
}
WalletBackendStatus LiteWalletGatewayRefreshAdapter::status() const
{
return gateway_.status();
}
const char* liteWalletRefreshRouteKindName(LiteWalletRefreshRouteKind route)
{
switch (route) {
case LiteWalletRefreshRouteKind::FullNodeRpc: return "FullNodeRpc";
case LiteWalletRefreshRouteKind::LiteGateway: return "LiteGateway";
case LiteWalletRefreshRouteKind::Unavailable: return "Unavailable";
}
return "Unknown";
}
LiteWalletRefreshRouteKind liteWalletRefreshRouteForCapabilities(const WalletCapabilities& capabilities)
{
if (capabilities.fullNodeRpcAvailable) return LiteWalletRefreshRouteKind::FullNodeRpc;
if (isLiteBuild(capabilities) && supportsLiteBackend(capabilities)) {
return LiteWalletRefreshRouteKind::LiteGateway;
}
return LiteWalletRefreshRouteKind::Unavailable;
}
LiteWalletRefreshService::LiteWalletRefreshService(WalletCapabilities capabilities,
LiteWalletRefreshGateway* liteGateway,
LiteWalletRefreshServiceOptions options)
: capabilities_(capabilities), liteGateway_(liteGateway), options_(options)
{
}
LiteWalletRefreshRouteKind LiteWalletRefreshService::route() const
{
return liteWalletRefreshRouteForCapabilities(capabilities_);
}
WalletBackendStatus LiteWalletRefreshService::status() const
{
const auto selectedRoute = route();
if (selectedRoute == LiteWalletRefreshRouteKind::LiteGateway) {
if (!liteGateway_) return statusForRoute(selectedRoute, "lite wallet gateway is not configured");
return liteGateway_->status();
}
return statusForRoute(selectedRoute);
}
LiteWalletRefreshServicePlan LiteWalletRefreshService::planRefresh(
const LiteWalletRefreshRequest& request) const
{
LiteWalletRefreshServicePlan plan;
plan.route = route();
if (plan.route == LiteWalletRefreshRouteKind::FullNodeRpc) {
plan.ok = true;
plan.fullNodeRefreshDelegated = true;
plan.status = statusForRoute(plan.route);
return plan;
}
if (plan.route == LiteWalletRefreshRouteKind::Unavailable) {
plan.status = statusForRoute(plan.route);
plan.error = plan.status.message;
return plan;
}
if (!liteGateway_) {
plan.status = statusForRoute(plan.route, "lite wallet gateway is not configured");
plan.error = plan.status.message;
return plan;
}
plan.liteGatewayConfigured = true;
plan.liteGatewayExecutionAllowed = options_.allowLiteGatewayRefresh;
plan.liteGatewayPlan = liteGateway_->planRefresh(request);
if (!plan.liteGatewayPlan.ok) {
plan.status = makeRefreshStatus(WalletBackendState::Error, plan.liteGatewayPlan.error);
plan.error = plan.status.message;
return plan;
}
plan.ok = true;
plan.status = liteGateway_->status();
return plan;
}
LiteWalletRefreshServiceResult LiteWalletRefreshService::refresh(
const LiteWalletRefreshRequest& request)
{
auto plan = planRefresh(request);
if (!plan.ok) return resultFromPlan(plan);
if (plan.route == LiteWalletRefreshRouteKind::FullNodeRpc) {
auto result = resultFromPlan(plan);
result.ok = true;
return result;
}
if (!plan.liteGatewayExecutionAllowed) {
plan.status = makeRefreshStatus(
WalletBackendState::Unavailable,
"lite wallet refresh gateway execution is disabled");
plan.error = plan.status.message;
return resultFromPlan(plan);
}
LiteWalletRefreshServiceResult result;
result.plan = plan;
result.liteGatewayCalled = true;
result.liteGatewayResult = liteGateway_->refresh(request);
result.attempted = result.liteGatewayResult.attempted;
result.bundle = result.liteGatewayResult.bundle;
result.status = result.liteGatewayResult.status;
result.error = result.liteGatewayResult.error;
result.ok = result.liteGatewayResult.ok;
return result;
}
WalletBackendStatus LiteWalletRefreshService::statusForRoute(
LiteWalletRefreshRouteKind selectedRoute,
const std::string& detail) const
{
switch (selectedRoute) {
case LiteWalletRefreshRouteKind::FullNodeRpc:
return makeRefreshStatus(
WalletBackendState::Disconnected,
detail.empty()
? "full-node refresh remains handled by existing NetworkRefreshService"
: detail);
case LiteWalletRefreshRouteKind::LiteGateway:
return makeRefreshStatus(
detail.empty() ? WalletBackendState::Disconnected : WalletBackendState::Unavailable,
detail.empty()
? "lite wallet refresh route is ready for gateway planning"
: detail);
case LiteWalletRefreshRouteKind::Unavailable:
return makeRefreshStatus(
WalletBackendState::Unavailable,
detail.empty() ? "lite backend is not linked" : detail);
}
return makeRefreshStatus(WalletBackendState::Unavailable, "unknown wallet refresh route");
}
LiteWalletRefreshServiceResult LiteWalletRefreshService::resultFromPlan(
const LiteWalletRefreshServicePlan& plan) const
{
LiteWalletRefreshServiceResult result;
result.plan = plan;
result.fullNodeRefreshDelegated = plan.fullNodeRefreshDelegated;
result.status = plan.status;
result.error = plan.error;
return result;
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,92 @@
#pragma once
#include "lite_wallet_gateway.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"
#include <string>
namespace dragonx::wallet {
enum class LiteWalletRefreshRouteKind {
FullNodeRpc,
LiteGateway,
Unavailable,
};
struct LiteWalletRefreshServiceOptions {
bool allowLiteGatewayRefresh = false;
};
struct LiteWalletRefreshServicePlan {
bool ok = false;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
bool fullNodeRefreshDelegated = false;
bool liteGatewayConfigured = false;
bool liteGatewayExecutionAllowed = false;
LiteWalletRefreshPlan liteGatewayPlan;
WalletBackendStatus status;
std::string error;
};
struct LiteWalletRefreshServiceResult {
bool ok = false;
bool attempted = false;
bool fullNodeRefreshDelegated = false;
bool liteGatewayCalled = false;
LiteWalletRefreshServicePlan plan;
LiteWalletRefreshResult liteGatewayResult;
LiteWalletRefreshBundle bundle;
WalletBackendStatus status;
std::string error;
};
class LiteWalletRefreshGateway {
public:
virtual ~LiteWalletRefreshGateway() = default;
virtual LiteWalletRefreshPlan planRefresh(const LiteWalletRefreshRequest& request) const = 0;
virtual LiteWalletRefreshResult refresh(const LiteWalletRefreshRequest& request) = 0;
virtual WalletBackendStatus status() const = 0;
};
class LiteWalletGatewayRefreshAdapter final : public LiteWalletRefreshGateway {
public:
explicit LiteWalletGatewayRefreshAdapter(LiteWalletGateway& gateway);
LiteWalletRefreshPlan planRefresh(const LiteWalletRefreshRequest& request) const override;
LiteWalletRefreshResult refresh(const LiteWalletRefreshRequest& request) override;
WalletBackendStatus status() const override;
private:
LiteWalletGateway& gateway_;
};
const char* liteWalletRefreshRouteKindName(LiteWalletRefreshRouteKind route);
LiteWalletRefreshRouteKind liteWalletRefreshRouteForCapabilities(const WalletCapabilities& capabilities);
class LiteWalletRefreshService {
public:
LiteWalletRefreshService(WalletCapabilities capabilities,
LiteWalletRefreshGateway* liteGateway = nullptr,
LiteWalletRefreshServiceOptions options = {});
const WalletCapabilities& capabilities() const { return capabilities_; }
const LiteWalletRefreshServiceOptions& options() const { return options_; }
LiteWalletRefreshRouteKind route() const;
WalletBackendStatus status() const;
LiteWalletRefreshServicePlan planRefresh(const LiteWalletRefreshRequest& request) const;
LiteWalletRefreshServiceResult refresh(const LiteWalletRefreshRequest& request);
private:
WalletBackendStatus statusForRoute(LiteWalletRefreshRouteKind route,
const std::string& detail = {}) const;
LiteWalletRefreshServiceResult resultFromPlan(const LiteWalletRefreshServicePlan& plan) const;
WalletCapabilities capabilities_;
LiteWalletRefreshGateway* liteGateway_ = nullptr;
LiteWalletRefreshServiceOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,407 @@
#include "wallet/lite_wallet_server_lifecycle_readiness.h"
#include <cctype>
#include <utility>
namespace dragonx::wallet {
namespace {
std::string trimCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
void addIssue(LiteWalletServerLifecycleReadinessResult& result,
LiteWalletServerLifecycleReadinessIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletServerLifecycleReadinessIssueInfo{issue, std::move(message)});
}
LiteWalletServerLifecycleReadinessResult stoppedResult(
LiteWalletServerLifecycleReadinessResult result,
LiteWalletServerLifecycleReadinessStatus status,
LiteWalletServerLifecycleReadinessIssue issue,
std::string message)
{
result.status = status;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
result.lifecycleStatus = WalletBackendStatus{WalletBackendState::Unavailable, result.error, {}, {}, 0.0};
result.syncLifecycleInput.status = result.lifecycleStatus;
return result;
}
LiteRedactedPrivateData redactedPrivateField(LitePrivateDataKind kind, const std::string& value)
{
return LiteRedactedPrivateData{kind, !trimCopy(value).empty(), redactLitePrivateDataValue(value)};
}
bool privateDataIsRedacted(const std::vector<LiteRedactedPrivateData>& privateData)
{
for (const auto& item : privateData) {
if (!item.present && item.redactedValue != "<empty>") return false;
if (item.present && item.redactedValue != "<redacted>") return false;
}
return true;
}
LiteWalletSelectedServerDisplayReport displayReportForSelection(
const LiteServerSelectionResult& selection)
{
LiteWalletSelectedServerDisplayReport report;
if (!selection.ok) {
report.status = LiteWalletSelectedServerDisplayStatus::Missing;
report.message = selection.error.empty() ? "no usable lite server is selected" : selection.error;
return report;
}
report.ok = true;
report.status = selection.customServer
? LiteWalletSelectedServerDisplayStatus::CustomServer
: LiteWalletSelectedServerDisplayStatus::SelectedServer;
report.label = selection.server.label;
report.url = selection.server.url;
report.serverIndex = selection.serverIndex;
report.customServer = selection.customServer;
report.message = selection.customServer
? "custom lite server selected for display"
: "configured lite server selected for display";
return report;
}
LiteWalletLifecyclePlan lifecyclePlanForRequest(
LiteWalletLifecycleOperation operation,
const LiteServerSelectionResult& selection)
{
LiteWalletLifecyclePlan plan;
plan.operation = operation;
plan.bridgeExecutionAllowed = false;
if (!selection.ok) {
plan.error = selection.error.empty() ? "no usable lite server is selected" : selection.error;
return plan;
}
plan.ok = true;
plan.server = selection.server;
plan.serverIndex = selection.serverIndex;
plan.customServer = selection.customServer;
return plan;
}
LiteWalletLifecycleUiRequestPlan requestPlanForInput(
const LiteWalletServerLifecycleReadinessInput& input,
const LiteServerSelectionResult& selection)
{
LiteWalletLifecycleUiRequestPlan plan;
plan.operation = input.operation;
plan.lifecyclePlan = lifecyclePlanForRequest(input.operation, selection);
if (!plan.lifecyclePlan.ok) {
plan.error = plan.lifecyclePlan.error;
return plan;
}
switch (input.operation) {
case LiteWalletLifecycleOperation::CreateNew:
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::Passphrase,
input.createRequest.passphrase));
plan.requestSummary = "operation=create;server=<selected>;passphrase=" +
std::string(input.createRequest.passphrase.empty() ? "<empty>" : "<redacted>");
break;
case LiteWalletLifecycleOperation::OpenExisting:
if (trimCopy(input.openRequest.walletPath).empty()) {
plan.error = "lite wallet open requires a wallet path selected by the UI";
return plan;
}
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::WalletPath,
input.openRequest.walletPath));
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::Passphrase,
input.openRequest.passphrase));
plan.requestSummary = "operation=open;server=<selected>;wallet_path=<redacted>;passphrase=" +
std::string(input.openRequest.passphrase.empty() ? "<empty>" : "<redacted>");
break;
case LiteWalletLifecycleOperation::RestoreFromSeed:
if (trimCopy(input.restoreRequest.seedPhrase).empty()) {
plan.error = "lite wallet restore requires redacted seed material metadata";
return plan;
}
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::SeedPhrase,
input.restoreRequest.seedPhrase));
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::WalletPath,
input.restoreRequest.walletPath));
plan.privateData.push_back(redactedPrivateField(
LitePrivateDataKind::Passphrase,
input.restoreRequest.passphrase));
plan.requestSummary = "operation=restore;server=<selected>;seed=<redacted>;wallet_path=" +
std::string(input.restoreRequest.walletPath.empty() ? "<empty>" : "<redacted>") +
";passphrase=" +
std::string(input.restoreRequest.passphrase.empty() ? "<empty>" : "<redacted>");
break;
}
plan.privateInputsRedacted = privateDataIsRedacted(plan.privateData);
plan.lifecyclePlan.privateData = plan.privateData;
plan.ok = plan.privateInputsRedacted;
if (!plan.ok) plan.error = "lite wallet lifecycle private inputs are not redacted";
return plan;
}
LiteWalletServerSelectionPersistencePlan persistencePlanForInput(
const LiteWalletServerLifecycleReadinessInput& input,
const LiteServerSelectionResult& selection)
{
LiteWalletServerSelectionPersistencePlan plan;
plan.settingsLoaded = input.persistence.settingsLoaded;
plan.persistedSelectionIntentAccepted = input.persistence.havePersistedSelectionIntent;
plan.wouldPersistSelectedServer = input.persistence.persistSelectedServer;
plan.persistenceOwnerAccepted = input.persistence.persistenceOwnerReady;
plan.selectionMode = input.settings.selectionMode;
if (selection.ok) {
plan.selectedServerUrl = selection.server.url;
plan.selectedServerIndex = selection.serverIndex;
plan.selectedServerCustom = selection.customServer;
}
plan.settingsWritten = false;
plan.ok = true;
return plan;
}
WalletBackendStatus readyLifecycleStatus(const LiteWalletSelectedServerDisplayReport& display)
{
const std::string serverLabel = display.label.empty() ? display.url : display.label;
return WalletBackendStatus{
WalletBackendState::Disconnected,
"lite lifecycle UI readiness accepted for " + serverLabel + "; wallet lifecycle execution is still disabled",
{},
{},
0.0
};
}
} // namespace
const char* liteWalletServerLifecycleReadinessStatusName(
LiteWalletServerLifecycleReadinessStatus status)
{
switch (status) {
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: return "ReadyForFutureLifecycle";
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild";
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability";
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: return "WaitingForServerSelection";
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: return "WaitingForPersistenceIntent";
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: return "WaitingForDisplayStatus";
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: return "WaitingForLifecycleUi";
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: return "WaitingForPrivateDataRedaction";
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: return "WaitingForSyncPlannerFeed";
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: return "RuntimeExecutionDisabled";
}
return "Unknown";
}
const char* liteWalletServerLifecycleReadinessIssueName(
LiteWalletServerLifecycleReadinessIssue issue)
{
switch (issue) {
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: return "FullNodeBuild";
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing";
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: return "PersistedSettingsNotLoaded";
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: return "PersistedServerSelectionIntentMissing";
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: return "ServerSelectionMissing";
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: return "ServerPersistenceOwnerMissing";
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: return "SelectedServerDisplayMissing";
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: return "LifecycleUiOwnerMissing";
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: return "LifecycleOperationUnconfirmed";
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: return "OpenWalletPathMissing";
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: return "RestoreSeedMissing";
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: return "PrivateDataRedactionMissing";
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: return "SyncPlannerFeedMissing";
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: return "RealLifecycleExecutionRequested";
}
return "Unknown";
}
const char* liteWalletSelectedServerDisplayStatusName(
LiteWalletSelectedServerDisplayStatus status)
{
switch (status) {
case LiteWalletSelectedServerDisplayStatus::Hidden: return "Hidden";
case LiteWalletSelectedServerDisplayStatus::SelectedServer: return "SelectedServer";
case LiteWalletSelectedServerDisplayStatus::CustomServer: return "CustomServer";
case LiteWalletSelectedServerDisplayStatus::Missing: return "Missing";
}
return "Unknown";
}
LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness(
const LiteWalletServerLifecycleReadinessInput& input,
LiteWalletServerLifecycleReadinessOptions options)
{
LiteWalletServerLifecycleReadinessResult result;
result.capabilities = input.capabilities;
result.persistencePlan = persistencePlanForInput(input, {});
if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild,
LiteWalletServerLifecycleReadinessIssue::FullNodeBuild,
"lite server lifecycle readiness requires a lite build");
}
result.liteBuildAccepted = true;
if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability,
LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing,
"lite backend capability is required before lifecycle UI readiness can feed sync planners");
}
result.backendCapabilityAccepted = true;
if (options.requirePersistedSettingsLoaded && !input.persistence.settingsLoaded) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded,
"lite server settings must be loaded before lifecycle UI readiness is evaluated");
}
if (options.requirePersistedSelectionIntent && !input.persistence.havePersistedSelectionIntent) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing,
"lite server selection persistence intent is missing");
}
result.selectedServer = selectLiteServer(input.settings);
result.persistencePlan = persistencePlanForInput(input, result.selectedServer);
if (!result.selectedServer.ok) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection,
LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing,
result.selectedServer.error.empty()
? "no usable lite server is selected"
: result.selectedServer.error);
}
result.serverSelectionAccepted = true;
if (input.persistence.persistSelectedServer && !input.persistence.persistenceOwnerReady) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing,
"lite server selection persistence owner is not ready");
}
result.persistenceIntentAccepted = true;
result.persistencePlan.ok = true;
result.selectedServerDisplay = displayReportForSelection(result.selectedServer);
if (options.requireSelectedServerDisplay && !input.ui.selectedServerDisplayReady) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus,
LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing,
"selected lite server display status is not ready");
}
result.selectedServerDisplayAccepted = true;
if (options.requireLifecycleUiOwner && !input.ui.lifecycleUiOwnerReady) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing,
"lite wallet lifecycle UI owner is not ready");
}
result.lifecycleUiOwnerAccepted = true;
if (input.ui.realLifecycleExecutionRequested) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled,
LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested,
"real lite wallet lifecycle execution is disabled in this scaffold");
}
if (options.requireOperationConfirmation && !input.ui.operationConfirmed) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed,
"lite wallet lifecycle operation requires explicit UI confirmation");
}
result.requestPlan = requestPlanForInput(input, result.selectedServer);
if (!result.requestPlan.ok) {
const auto issue = input.operation == LiteWalletLifecycleOperation::OpenExisting
? LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing
: LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing;
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
issue,
result.requestPlan.error.empty()
? "lite wallet lifecycle UI request is incomplete"
: result.requestPlan.error);
}
result.requestAccepted = true;
if (options.requirePrivateDataRedaction && !input.ui.privateDataRedactionReady) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction,
LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing,
"lite wallet lifecycle private-data redaction owner is not ready");
}
result.privateDataRedactionAccepted = true;
if (options.requireSyncPlannerFeed && !input.ui.syncPlannerFeedReady) {
return stoppedResult(
std::move(result),
LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed,
LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing,
"lite lifecycle readiness feed for sync planners is not ready");
}
result.syncPlannerFeedAccepted = true;
result.ok = true;
result.status = LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle;
result.futureLifecycleCouldBeEnabled = true;
result.lifecycleReportCouldFeedSyncPlanners = true;
result.lifecycleStatus = readyLifecycleStatus(result.selectedServerDisplay);
result.syncLifecycleInput.ready = true;
result.syncLifecycleInput.status = result.lifecycleStatus;
return result;
}
LiteWalletSyncAppRefreshLifecycleInput liteWalletSyncLifecycleInputFromServerLifecycleReadiness(
const LiteWalletServerLifecycleReadinessResult& result)
{
return result.syncLifecycleInput;
}
LiteWalletServerLifecycleReadinessPlanner::LiteWalletServerLifecycleReadinessPlanner(
LiteWalletServerLifecycleReadinessOptions options)
: options_(options)
{
}
LiteWalletServerLifecycleReadinessResult LiteWalletServerLifecycleReadinessPlanner::evaluate(
const LiteWalletServerLifecycleReadinessInput& input) const
{
return evaluateLiteWalletServerLifecycleReadiness(input, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,197 @@
#pragma once
#include "lite_wallet_lifecycle_service.h"
#include "lite_wallet_sync_app_refresh_integration.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletServerLifecycleReadinessStatus {
ReadyForFutureLifecycle,
WaitingForLiteBuild,
WaitingForBackendCapability,
WaitingForServerSelection,
WaitingForPersistenceIntent,
WaitingForDisplayStatus,
WaitingForLifecycleUi,
WaitingForPrivateDataRedaction,
WaitingForSyncPlannerFeed,
RuntimeExecutionDisabled,
};
enum class LiteWalletServerLifecycleReadinessIssue {
FullNodeBuild,
LiteBackendCapabilityMissing,
PersistedSettingsNotLoaded,
PersistedServerSelectionIntentMissing,
ServerSelectionMissing,
ServerPersistenceOwnerMissing,
SelectedServerDisplayMissing,
LifecycleUiOwnerMissing,
LifecycleOperationUnconfirmed,
OpenWalletPathMissing,
RestoreSeedMissing,
PrivateDataRedactionMissing,
SyncPlannerFeedMissing,
RealLifecycleExecutionRequested,
};
enum class LiteWalletSelectedServerDisplayStatus {
Hidden,
SelectedServer,
CustomServer,
Missing,
};
struct LiteWalletServerSelectionPersistenceInput {
bool settingsLoaded = false;
bool havePersistedSelectionIntent = false;
bool persistSelectedServer = false;
bool persistenceOwnerReady = false;
};
struct LiteWalletLifecycleUiPrerequisites {
bool selectedServerDisplayReady = false;
bool lifecycleUiOwnerReady = false;
bool operationConfirmed = false;
bool privateDataRedactionReady = false;
bool syncPlannerFeedReady = false;
bool realLifecycleExecutionRequested = false;
};
struct LiteWalletServerLifecycleReadinessInput {
WalletCapabilities capabilities;
LiteConnectionSettings settings;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteWalletServerSelectionPersistenceInput persistence;
LiteWalletLifecycleUiPrerequisites ui;
LiteWalletCreateRequest createRequest;
LiteWalletOpenRequest openRequest;
LiteWalletRestoreRequest restoreRequest;
};
struct LiteWalletServerLifecycleReadinessOptions {
bool requireLiteBuild = true;
bool requireLiteBackendCapability = true;
bool requirePersistedSettingsLoaded = true;
bool requirePersistedSelectionIntent = true;
bool requireSelectedServerDisplay = true;
bool requireLifecycleUiOwner = true;
bool requireOperationConfirmation = true;
bool requirePrivateDataRedaction = true;
bool requireSyncPlannerFeed = true;
};
struct LiteWalletSelectedServerDisplayReport {
bool ok = false;
LiteWalletSelectedServerDisplayStatus status = LiteWalletSelectedServerDisplayStatus::Hidden;
std::string label;
std::string url;
std::size_t serverIndex = 0;
bool customServer = false;
std::string message;
};
struct LiteWalletServerSelectionPersistencePlan {
bool ok = false;
bool settingsLoaded = false;
bool persistedSelectionIntentAccepted = false;
bool wouldPersistSelectedServer = false;
bool persistenceOwnerAccepted = false;
bool settingsWritten = false;
LiteServerSelectionMode selectionMode = LiteServerSelectionMode::Sticky;
std::string selectedServerUrl;
std::size_t selectedServerIndex = 0;
bool selectedServerCustom = false;
std::string error;
};
struct LiteWalletLifecycleUiRequestPlan {
bool ok = false;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteWalletLifecyclePlan lifecyclePlan;
std::vector<LiteRedactedPrivateData> privateData;
bool privateInputsRedacted = true;
std::string requestSummary;
std::string error;
};
struct LiteWalletServerLifecycleReadinessIssueInfo {
LiteWalletServerLifecycleReadinessIssue issue = LiteWalletServerLifecycleReadinessIssue::FullNodeBuild;
std::string message;
};
struct LiteWalletServerLifecycleReadinessResult {
bool ok = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool noBridgeCalls = true;
bool noServerHealthChecked = true;
bool noWalletExistsChecked = true;
bool noWalletCreated = true;
bool noWalletOpened = true;
bool noWalletRestored = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWalletPersistence = true;
bool noSettingsPersistence = true;
bool settingsWritten = false;
bool workerQueueEnqueued = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool walletReady = false;
bool liteBuildAccepted = false;
bool backendCapabilityAccepted = false;
bool serverSelectionAccepted = false;
bool persistenceIntentAccepted = false;
bool selectedServerDisplayAccepted = false;
bool lifecycleUiOwnerAccepted = false;
bool requestAccepted = false;
bool privateDataRedactionAccepted = false;
bool syncPlannerFeedAccepted = false;
bool futureLifecycleCouldBeEnabled = false;
bool lifecycleReportCouldFeedSyncPlanners = false;
LiteWalletServerLifecycleReadinessStatus status = LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild;
WalletCapabilities capabilities;
LiteServerSelectionResult selectedServer;
LiteWalletSelectedServerDisplayReport selectedServerDisplay;
LiteWalletServerSelectionPersistencePlan persistencePlan;
LiteWalletLifecycleUiRequestPlan requestPlan;
LiteWalletSyncAppRefreshLifecycleInput syncLifecycleInput;
WalletBackendStatus lifecycleStatus;
std::vector<LiteWalletServerLifecycleReadinessIssueInfo> issues;
std::string error;
};
const char* liteWalletServerLifecycleReadinessStatusName(
LiteWalletServerLifecycleReadinessStatus status);
const char* liteWalletServerLifecycleReadinessIssueName(
LiteWalletServerLifecycleReadinessIssue issue);
const char* liteWalletSelectedServerDisplayStatusName(
LiteWalletSelectedServerDisplayStatus status);
LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness(
const LiteWalletServerLifecycleReadinessInput& input,
LiteWalletServerLifecycleReadinessOptions options = {});
LiteWalletSyncAppRefreshLifecycleInput liteWalletSyncLifecycleInputFromServerLifecycleReadiness(
const LiteWalletServerLifecycleReadinessResult& result);
class LiteWalletServerLifecycleReadinessPlanner {
public:
explicit LiteWalletServerLifecycleReadinessPlanner(
LiteWalletServerLifecycleReadinessOptions options = {});
LiteWalletServerLifecycleReadinessResult evaluate(
const LiteWalletServerLifecycleReadinessInput& input) const;
private:
LiteWalletServerLifecycleReadinessOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,463 @@
#include "lite_wallet_server_selection_adapter.h"
#include <algorithm>
#include <cctype>
#include <sstream>
#include <utility>
namespace dragonx {
namespace wallet {
namespace {
std::string trimCopy(const std::string& value)
{
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
return std::isspace(ch) != 0;
});
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
return std::isspace(ch) != 0;
}).base();
if (first >= last) return {};
return std::string(first, last);
}
LiteServerSelectionMode walletModeFromSettings(
config::Settings::LiteServerSelectionPreferenceMode mode)
{
switch (mode) {
case config::Settings::LiteServerSelectionPreferenceMode::Sticky:
return LiteServerSelectionMode::Sticky;
case config::Settings::LiteServerSelectionPreferenceMode::Random:
return LiteServerSelectionMode::Random;
}
return LiteServerSelectionMode::Sticky;
}
config::Settings::LiteServerSelectionPreferenceMode settingsModeFromWallet(
LiteServerSelectionMode mode)
{
switch (mode) {
case LiteServerSelectionMode::Sticky:
return config::Settings::LiteServerSelectionPreferenceMode::Sticky;
case LiteServerSelectionMode::Random:
return config::Settings::LiteServerSelectionPreferenceMode::Random;
}
return config::Settings::LiteServerSelectionPreferenceMode::Sticky;
}
void addIssue(std::vector<LiteWalletServerSelectionUiExecutionIssueInfo>& issues,
LiteWalletServerSelectionUiExecutionIssue issue,
std::string message)
{
issues.push_back(LiteWalletServerSelectionUiExecutionIssueInfo{issue, std::move(message)});
}
LiteWalletServerSelectionUiExecutionStatus statusFromLifecycle(
LiteWalletServerLifecycleReadinessStatus status)
{
switch (status) {
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle:
return LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild;
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings;
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection;
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus;
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi;
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction;
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed:
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed;
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled:
return LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled;
}
return LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings;
}
LiteWalletServerSelectionUiExecutionIssue issueFromLifecycle(
LiteWalletServerLifecycleReadinessIssue issue)
{
switch (issue) {
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild:
return LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild;
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing:
return LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing;
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded:
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing:
return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded;
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing:
return LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing;
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing:
return LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing;
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing:
return LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing;
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing:
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed:
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing:
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing:
return LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing;
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing:
return LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing;
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing:
return LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing;
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested:
return LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested;
}
return LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded;
}
void copyLifecycleIssues(LiteWalletServerSelectionUiExecutionResult& result)
{
for (const auto& issue : result.lifecycleReadiness.issues) {
addIssue(result.issues, issueFromLifecycle(issue.issue), issue.message);
}
}
LiteWalletServerSelectionUiExecutionResult stoppedResult(
LiteWalletServerSelectionUiExecutionResult result,
LiteWalletServerSelectionUiExecutionStatus status,
LiteWalletServerSelectionUiExecutionIssue issue,
const std::string& message)
{
result.status = status;
result.error = message;
result.ok = false;
addIssue(result.issues, issue, message);
return result;
}
std::string buildDiagnosticSummary(const LiteWalletServerSelectionUiExecutionResult& result)
{
std::ostringstream out;
out << "mode=" << liteServerSelectionModeName(result.connectionSettings.selectionMode)
<< ";server=" << result.selectedServerUrlRedacted
<< ";settings_written=" << (result.settingsWritten ? "true" : "false")
<< ";network=false;bridge=false;lifecycle=false;sync=false;wallet_state=false";
return out.str();
}
LiteConnectionSettings settingsWithIntent(const LiteConnectionSettings& current,
const LiteWalletServerSelectionUiIntent& intent)
{
LiteConnectionSettings settings = current;
if (!intent.selectedServerIntentProvided) return settings;
settings.selectionMode = intent.selectionMode;
if (!intent.chainName.empty()) settings.chainName = trimCopy(intent.chainName);
if (intent.replaceServers) settings.servers = intent.servers;
switch (intent.selectionMode) {
case LiteServerSelectionMode::Sticky:
settings.stickyServerUrl = trimCopy(intent.selectedServerUrl);
break;
case LiteServerSelectionMode::Random:
settings.randomSelectionSeed = intent.randomSelectionSeed;
break;
}
return settings;
}
LiteWalletServerLifecycleReadinessInput makeLifecycleInput(
const LiteWalletServerSelectionUiExecutionInput& input,
const LiteConnectionSettings& connectionSettings)
{
LiteWalletServerLifecycleReadinessInput lifecycle;
lifecycle.capabilities = input.capabilities;
lifecycle.settings = connectionSettings;
lifecycle.operation = input.operation;
lifecycle.persistence.settingsLoaded = input.persistence.settingsLoaded;
lifecycle.persistence.havePersistedSelectionIntent =
input.persistence.havePersistedSelectionIntent || input.intent.selectedServerIntentProvided;
lifecycle.persistence.persistSelectedServer = input.persistence.persistSelectedServer;
lifecycle.persistence.persistenceOwnerReady = input.persistence.persistenceOwnerReady;
lifecycle.ui = input.ui;
lifecycle.ui.realLifecycleExecutionRequested =
lifecycle.ui.realLifecycleExecutionRequested || input.walletLifecycleExecutionRequested;
lifecycle.createRequest = input.createRequest;
lifecycle.openRequest = input.openRequest;
lifecycle.restoreRequest = input.restoreRequest;
return lifecycle;
}
} // namespace
LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Settings& settings)
{
LiteConnectionSettings connectionSettings;
connectionSettings.servers.clear();
for (const auto& server : settings.getLiteServers()) {
connectionSettings.servers.push_back(LiteServerEndpoint{
trimCopy(server.url),
server.label,
server.enabled
});
}
connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode());
connectionSettings.stickyServerUrl = trimCopy(settings.getLiteStickyServerUrl());
connectionSettings.chainName = trimCopy(settings.getLiteChainName());
if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName;
connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed();
return connectionSettings;
}
void applyLiteConnectionSettingsToAppSettings(config::Settings& settings,
const LiteConnectionSettings& connectionSettings)
{
std::vector<config::Settings::LiteServerPreference> servers;
servers.reserve(connectionSettings.servers.size());
for (const auto& server : connectionSettings.servers) {
servers.push_back(config::Settings::LiteServerPreference{
trimCopy(server.url),
server.label,
server.enabled
});
}
settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode));
settings.setLiteStickyServerUrl(trimCopy(connectionSettings.stickyServerUrl));
settings.setLiteChainName(trimCopy(connectionSettings.chainName).empty()
? kDragonXLiteChainName
: trimCopy(connectionSettings.chainName));
settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed);
settings.setLiteServers(servers);
}
const char* liteWalletServerSelectionUiExecutionStatusName(
LiteWalletServerSelectionUiExecutionStatus status)
{
switch (status) {
case LiteWalletServerSelectionUiExecutionStatus::ReadyForFutureLifecycle:
return "ReadyForFutureLifecycle";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLiteBuild:
return "WaitingForLiteBuild";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForBackendCapability:
return "WaitingForBackendCapability";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings:
return "WaitingForSettings";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection:
return "WaitingForServerSelection";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner:
return "WaitingForPersistenceOwner";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForDisplayStatus:
return "WaitingForDisplayStatus";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForLifecycleUi:
return "WaitingForLifecycleUi";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForPrivateDataRedaction:
return "WaitingForPrivateDataRedaction";
case LiteWalletServerSelectionUiExecutionStatus::WaitingForSyncPlannerFeed:
return "WaitingForSyncPlannerFeed";
case LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed:
return "SettingsSaveFailed";
case LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled:
return "RuntimeExecutionDisabled";
}
return "WaitingForSettings";
}
const char* liteWalletServerSelectionUiExecutionIssueName(
LiteWalletServerSelectionUiExecutionIssue issue)
{
switch (issue) {
case LiteWalletServerSelectionUiExecutionIssue::FullNodeBuild:
return "FullNodeBuild";
case LiteWalletServerSelectionUiExecutionIssue::LiteBackendCapabilityMissing:
return "LiteBackendCapabilityMissing";
case LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded:
return "SettingsNotLoaded";
case LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing:
return "ServerSelectionMissing";
case LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing:
return "ServerPersistenceOwnerMissing";
case LiteWalletServerSelectionUiExecutionIssue::SelectedServerDisplayMissing:
return "SelectedServerDisplayMissing";
case LiteWalletServerSelectionUiExecutionIssue::LifecycleUiOwnerMissing:
return "LifecycleUiOwnerMissing";
case LiteWalletServerSelectionUiExecutionIssue::PrivateDataRedactionMissing:
return "PrivateDataRedactionMissing";
case LiteWalletServerSelectionUiExecutionIssue::SyncPlannerFeedMissing:
return "SyncPlannerFeedMissing";
case LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed:
return "SettingsSaveFailed";
case LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested:
return "ServerConnectivityCheckRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested:
return "WalletLifecycleExecutionRequested";
case LiteWalletServerSelectionUiExecutionIssue::SyncRequested:
return "SyncRequested";
case LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested:
return "SyncStatusPollingRequested";
case LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested:
return "WorkerQueueRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested:
return "WalletStateMutationRequested";
case LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested:
return "WalletFilePersistenceRequested";
case LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested:
return "SendImportExportRequested";
}
return "SettingsNotLoaded";
}
std::string redactLiteServerSelectionValue(const std::string& value)
{
const std::string trimmed = trimCopy(value);
if (trimmed.empty()) return "<empty>";
const auto scheme = trimmed.find("://");
if (scheme == std::string::npos) return "<redacted>";
return trimmed.substr(0, scheme + 3) + "<redacted>";
}
LiteWalletServerSelectionUiExecutionResult executeLiteWalletServerSelectionUi(
config::Settings& settings,
const LiteWalletServerSelectionUiExecutionInput& input)
{
LiteWalletServerSelectionUiExecutionResult result;
auto stopForRuntime = [&](LiteWalletServerSelectionUiExecutionIssue issue,
const std::string& message) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::RuntimeExecutionDisabled,
issue,
message);
};
if (input.serverConnectivityCheckRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested,
"lite server selection settings adapter cannot check server connectivity");
}
if (input.walletExistsCheckRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::ServerConnectivityCheckRequested,
"lite server selection settings adapter cannot check wallet existence");
}
if (input.walletLifecycleExecutionRequested || input.ui.realLifecycleExecutionRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletLifecycleExecutionRequested,
"lite server selection settings adapter cannot execute wallet lifecycle actions");
}
if (input.syncStartRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncRequested,
"lite server selection settings adapter cannot start sync");
}
if (input.syncStatusPollingRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SyncStatusPollingRequested,
"lite server selection settings adapter cannot poll syncstatus");
}
if (input.workerQueueEnqueueRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WorkerQueueRequested,
"lite server selection settings adapter cannot enqueue wallet workers");
}
if (input.walletStateMutationRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletStateMutationRequested,
"lite server selection settings adapter cannot mutate WalletState");
}
if (input.walletFilePersistenceRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::WalletFilePersistenceRequested,
"lite server selection settings adapter cannot persist wallet files");
}
if (input.sendImportExportExecutionRequested) {
return stopForRuntime(LiteWalletServerSelectionUiExecutionIssue::SendImportExportRequested,
"lite server selection settings adapter cannot execute send/import/export flows");
}
if (!input.persistence.settingsLoaded) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings,
LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded,
"lite server settings are not loaded");
}
result.connectionSettings = settingsWithIntent(
liteConnectionSettingsFromAppSettings(settings), input.intent);
if (input.intent.selectedServerIntentProvided &&
input.intent.selectionMode == LiteServerSelectionMode::Sticky &&
!isLiteServerUrlUsable(trimCopy(input.intent.selectedServerUrl))) {
result.selectedServerUrlRedacted = redactLiteServerSelectionValue(input.intent.selectedServerUrl);
result.selectedServerRedacted = true;
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection,
LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing,
"lite server selection URL is not usable");
}
result.selectedServer = selectLiteServer(result.connectionSettings);
result.selectedServerUrlRedacted = result.selectedServer.ok
? redactLiteServerSelectionValue(result.selectedServer.server.url)
: redactLiteServerSelectionValue(result.connectionSettings.stickyServerUrl);
result.selectedServerRedacted = true;
if (!result.selectedServer.ok) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForServerSelection,
LiteWalletServerSelectionUiExecutionIssue::ServerSelectionMissing,
result.selectedServer.error.empty()
? "lite server selection is missing"
: result.selectedServer.error);
}
result.selectedServerIntentAccepted = true;
if ((input.persistence.persistSelectedServer || input.persistence.writeSettings) &&
!input.persistence.persistenceOwnerReady) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::WaitingForPersistenceOwner,
LiteWalletServerSelectionUiExecutionIssue::ServerPersistenceOwnerMissing,
"lite server settings persistence owner is not ready");
}
result.settingsPersistenceAccepted = true;
if (input.persistence.persistSelectedServer || input.persistence.writeSettings) {
applyLiteConnectionSettingsToAppSettings(settings, result.connectionSettings);
settings.setLitePersistSelectedServer(input.persistence.persistSelectedServer);
result.settingsMutated = true;
}
if (input.persistence.writeSettings) {
result.noSettingsPersistence = false;
const bool saved = input.persistence.settingsSavePath.empty()
? settings.save()
: settings.save(input.persistence.settingsSavePath);
if (!saved) {
return stoppedResult(std::move(result),
LiteWalletServerSelectionUiExecutionStatus::SettingsSaveFailed,
LiteWalletServerSelectionUiExecutionIssue::SettingsSaveFailed,
"failed to save lite server settings");
}
result.settingsWritten = true;
}
result.lifecycleInput = makeLifecycleInput(input, result.connectionSettings);
result.lifecycleReadiness = evaluateLiteWalletServerLifecycleReadiness(result.lifecycleInput);
result.lifecycleReadinessAccepted = result.lifecycleReadiness.ok;
result.lifecycleReportCouldFeedSyncPlanners = result.lifecycleReadiness.lifecycleReportCouldFeedSyncPlanners;
result.status = statusFromLifecycle(result.lifecycleReadiness.status);
copyLifecycleIssues(result);
result.diagnosticSummary = buildDiagnosticSummary(result);
if (input.requireLifecycleReadiness && !result.lifecycleReadiness.ok) {
result.ok = false;
result.error = result.lifecycleReadiness.error;
return result;
}
result.ok = true;
return result;
}
LiteWalletServerSelectionUiExecutionAdapter::LiteWalletServerSelectionUiExecutionAdapter(
config::Settings& settings)
: settings_(settings)
{
}
LiteWalletServerSelectionUiExecutionResult LiteWalletServerSelectionUiExecutionAdapter::execute(
const LiteWalletServerSelectionUiExecutionInput& input) const
{
return executeLiteWalletServerSelectionUi(settings_, input);
}
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,161 @@
#pragma once
#include "lite_connection_service.h"
#include "lite_wallet_server_lifecycle_readiness.h"
#include "wallet_capabilities.h"
#include "../config/settings.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx {
namespace wallet {
enum class LiteWalletServerSelectionUiExecutionStatus {
ReadyForFutureLifecycle,
WaitingForLiteBuild,
WaitingForBackendCapability,
WaitingForSettings,
WaitingForServerSelection,
WaitingForPersistenceOwner,
WaitingForDisplayStatus,
WaitingForLifecycleUi,
WaitingForPrivateDataRedaction,
WaitingForSyncPlannerFeed,
SettingsSaveFailed,
RuntimeExecutionDisabled
};
enum class LiteWalletServerSelectionUiExecutionIssue {
FullNodeBuild,
LiteBackendCapabilityMissing,
SettingsNotLoaded,
ServerSelectionMissing,
ServerPersistenceOwnerMissing,
SelectedServerDisplayMissing,
LifecycleUiOwnerMissing,
PrivateDataRedactionMissing,
SyncPlannerFeedMissing,
SettingsSaveFailed,
ServerConnectivityCheckRequested,
WalletLifecycleExecutionRequested,
SyncRequested,
SyncStatusPollingRequested,
WorkerQueueRequested,
WalletStateMutationRequested,
WalletFilePersistenceRequested,
SendImportExportRequested
};
struct LiteWalletServerSelectionUiIntent {
bool selectedServerIntentProvided = false;
LiteServerSelectionMode selectionMode = LiteServerSelectionMode::Sticky;
std::string selectedServerUrl;
std::size_t randomSelectionSeed = 0;
std::string chainName;
bool replaceServers = false;
std::vector<LiteServerEndpoint> servers;
};
struct LiteWalletServerSelectionPersistenceExecutionInput {
bool settingsLoaded = true;
bool havePersistedSelectionIntent = true;
bool persistSelectedServer = true;
bool persistenceOwnerReady = true;
bool writeSettings = false;
std::string settingsSavePath;
};
struct LiteWalletServerSelectionUiExecutionInput {
WalletCapabilities capabilities;
LiteWalletServerSelectionUiIntent intent;
LiteWalletServerSelectionPersistenceExecutionInput persistence;
LiteWalletLifecycleUiPrerequisites ui;
LiteWalletLifecycleOperation operation = LiteWalletLifecycleOperation::CreateNew;
LiteWalletCreateRequest createRequest;
LiteWalletOpenRequest openRequest;
LiteWalletRestoreRequest restoreRequest;
bool requireLifecycleReadiness = false;
bool serverConnectivityCheckRequested = false;
bool walletExistsCheckRequested = false;
bool walletLifecycleExecutionRequested = false;
bool syncStartRequested = false;
bool syncStatusPollingRequested = false;
bool workerQueueEnqueueRequested = false;
bool walletStateMutationRequested = false;
bool walletFilePersistenceRequested = false;
bool sendImportExportExecutionRequested = false;
};
struct LiteWalletServerSelectionUiExecutionIssueInfo {
LiteWalletServerSelectionUiExecutionIssue issue = LiteWalletServerSelectionUiExecutionIssue::SettingsNotLoaded;
std::string message;
};
struct LiteWalletServerSelectionUiExecutionResult {
bool ok = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool noBridgeCalls = true;
bool noServerHealthChecked = true;
bool noWalletExistsChecked = true;
bool noWalletCreated = true;
bool noWalletOpened = true;
bool noWalletRestored = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWalletPersistence = true;
bool noSendImportExportExecution = true;
bool workerQueueEnqueued = false;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool settingsMutationAllowed = true;
bool settingsMutated = false;
bool noSettingsPersistence = true;
bool settingsWritten = false;
bool settingsPersistenceAccepted = false;
bool selectedServerIntentAccepted = false;
bool lifecycleReadinessAccepted = false;
bool lifecycleReportCouldFeedSyncPlanners = false;
bool selectedServerRedacted = false;
LiteWalletServerSelectionUiExecutionStatus status = LiteWalletServerSelectionUiExecutionStatus::WaitingForSettings;
std::string error;
std::string selectedServerUrlRedacted;
std::string diagnosticSummary;
LiteConnectionSettings connectionSettings;
LiteServerSelectionResult selectedServer;
LiteWalletServerLifecycleReadinessInput lifecycleInput;
LiteWalletServerLifecycleReadinessResult lifecycleReadiness;
std::vector<LiteWalletServerSelectionUiExecutionIssueInfo> issues;
};
LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Settings& settings);
void applyLiteConnectionSettingsToAppSettings(config::Settings& settings,
const LiteConnectionSettings& connectionSettings);
const char* liteWalletServerSelectionUiExecutionStatusName(
LiteWalletServerSelectionUiExecutionStatus status);
const char* liteWalletServerSelectionUiExecutionIssueName(
LiteWalletServerSelectionUiExecutionIssue issue);
std::string redactLiteServerSelectionValue(const std::string& value);
LiteWalletServerSelectionUiExecutionResult executeLiteWalletServerSelectionUi(
config::Settings& settings,
const LiteWalletServerSelectionUiExecutionInput& input);
class LiteWalletServerSelectionUiExecutionAdapter {
public:
explicit LiteWalletServerSelectionUiExecutionAdapter(config::Settings& settings);
LiteWalletServerSelectionUiExecutionResult execute(
const LiteWalletServerSelectionUiExecutionInput& input) const;
private:
config::Settings& settings_;
};
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,118 @@
#include "wallet/lite_wallet_state_apply_executor.h"
#include <utility>
namespace dragonx::wallet {
namespace {
void addIssue(LiteWalletStateApplyExecutionResult& result,
LiteWalletStateApplyExecutionIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletStateApplyExecutionIssueInfo{issue, std::move(message)});
}
std::size_t countPlannedChanges(const LiteWalletStateApplyPlan& plan)
{
std::size_t count = 0;
for (const auto& fieldPlan : plan.fieldPlans) {
if (fieldPlan.wouldChange) ++count;
}
for (const auto& collectionPlan : plan.collectionPlans) {
if (collectionPlan.wouldReplace) ++count;
}
return count;
}
LiteWalletStateApplyExecutionResult baseResult(const LiteWalletStateApplyPlan& plan,
LiteWalletStateApplyExecutorOptions options)
{
LiteWalletStateApplyExecutionResult result;
result.stateMutationRequested = options.requestStateMutation;
result.stateMutationAllowed = false;
result.applyImplemented = false;
result.fieldPlanCount = plan.fieldPlans.size();
result.collectionPlanCount = plan.collectionPlans.size();
result.plannedChangeCount = countPlannedChanges(plan);
result.planIssueCount = plan.issues.size();
return result;
}
} // namespace
const char* liteWalletStateApplyExecutionStatusName(LiteWalletStateApplyExecutionStatus status)
{
switch (status) {
case LiteWalletStateApplyExecutionStatus::Disabled: return "Disabled";
case LiteWalletStateApplyExecutionStatus::Rejected: return "Rejected";
case LiteWalletStateApplyExecutionStatus::ImplementationMissing: return "ImplementationMissing";
}
return "Unknown";
}
const char* liteWalletStateApplyExecutionIssueName(LiteWalletStateApplyExecutionIssue issue)
{
switch (issue) {
case LiteWalletStateApplyExecutionIssue::InvalidPlan: return "InvalidPlan";
case LiteWalletStateApplyExecutionIssue::MutablePlanRejected: return "MutablePlanRejected";
case LiteWalletStateApplyExecutionIssue::StateMutationDisabled: return "StateMutationDisabled";
case LiteWalletStateApplyExecutionIssue::StateMutationImplementationMissing: return "StateMutationImplementationMissing";
}
return "Unknown";
}
LiteWalletStateApplyExecutionResult executeLiteWalletStateApplyPlan(
const LiteWalletStateApplyPlan& plan,
LiteWalletStateApplyExecutorOptions options)
{
auto result = baseResult(plan, options);
if (!plan.ok) {
result.status = LiteWalletStateApplyExecutionStatus::Rejected;
addIssue(result,
LiteWalletStateApplyExecutionIssue::InvalidPlan,
"lite WalletState apply plan is not valid");
result.error = plan.error.empty() ? result.issues.back().message : plan.error;
return result;
}
if (!plan.dryRunOnly || plan.applyImplemented || plan.stateMutationAllowed) {
result.status = LiteWalletStateApplyExecutionStatus::Rejected;
addIssue(result,
LiteWalletStateApplyExecutionIssue::MutablePlanRejected,
"lite WalletState apply executor accepts dry-run-only plans only");
result.error = result.issues.back().message;
return result;
}
result.planAccepted = true;
if (options.requestStateMutation) {
result.status = LiteWalletStateApplyExecutionStatus::ImplementationMissing;
addIssue(result,
LiteWalletStateApplyExecutionIssue::StateMutationImplementationMissing,
"real lite WalletState application requires a future explicit implementation");
result.error = result.issues.back().message;
return result;
}
result.ok = true;
result.status = LiteWalletStateApplyExecutionStatus::Disabled;
addIssue(result,
LiteWalletStateApplyExecutionIssue::StateMutationDisabled,
"lite WalletState application is disabled; dry-run plan was accepted for reporting only");
return result;
}
LiteWalletStateApplyExecutor::LiteWalletStateApplyExecutor(LiteWalletStateApplyExecutorOptions options)
: options_(options)
{
}
LiteWalletStateApplyExecutionResult LiteWalletStateApplyExecutor::execute(
const LiteWalletStateApplyPlan& plan) const
{
return executeLiteWalletStateApplyPlan(plan, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,71 @@
#pragma once
#include "lite_wallet_state_apply_plan.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletStateApplyExecutionStatus {
Disabled,
Rejected,
ImplementationMissing,
};
enum class LiteWalletStateApplyExecutionIssue {
InvalidPlan,
MutablePlanRejected,
StateMutationDisabled,
StateMutationImplementationMissing,
};
struct LiteWalletStateApplyExecutorOptions {
bool requestStateMutation = false;
};
struct LiteWalletStateApplyExecutionIssueInfo {
LiteWalletStateApplyExecutionIssue issue = LiteWalletStateApplyExecutionIssue::StateMutationDisabled;
std::string message;
};
struct LiteWalletStateApplyExecutionResult {
bool ok = false;
bool planAccepted = false;
bool attempted = false;
bool applied = false;
bool stateMutated = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool stateMutationRequested = false;
bool stateMutationAllowed = false;
bool applyImplemented = false;
LiteWalletStateApplyExecutionStatus status = LiteWalletStateApplyExecutionStatus::Disabled;
std::size_t fieldPlanCount = 0;
std::size_t collectionPlanCount = 0;
std::size_t plannedChangeCount = 0;
std::size_t planIssueCount = 0;
std::vector<LiteWalletStateApplyExecutionIssueInfo> issues;
std::string error;
};
const char* liteWalletStateApplyExecutionStatusName(LiteWalletStateApplyExecutionStatus status);
const char* liteWalletStateApplyExecutionIssueName(LiteWalletStateApplyExecutionIssue issue);
LiteWalletStateApplyExecutionResult executeLiteWalletStateApplyPlan(
const LiteWalletStateApplyPlan& plan,
LiteWalletStateApplyExecutorOptions options = {});
class LiteWalletStateApplyExecutor {
public:
explicit LiteWalletStateApplyExecutor(LiteWalletStateApplyExecutorOptions options = {});
LiteWalletStateApplyExecutionResult execute(const LiteWalletStateApplyPlan& plan) const;
private:
LiteWalletStateApplyExecutorOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,405 @@
#include "wallet/lite_wallet_state_apply_plan.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <sstream>
#include <utility>
namespace dragonx::wallet {
namespace {
constexpr double kZatoshisPerCoin = 100000000.0;
std::string stringValue(const std::string& value)
{
return value;
}
std::string intValue(std::int64_t value)
{
return std::to_string(value);
}
std::string uintValue(std::uint64_t value)
{
return std::to_string(value);
}
std::string doubleValue(double value)
{
std::ostringstream output;
output << value;
return output.str();
}
std::string boolValue(bool value)
{
return value ? "true" : "false";
}
std::uint64_t coinsToZatoshis(double coins)
{
if (coins <= 0.0) return 0;
const double zatoshis = std::round(coins * kZatoshisPerCoin);
const double maxValue = static_cast<double>(std::numeric_limits<std::uint64_t>::max());
if (zatoshis >= maxValue) return std::numeric_limits<std::uint64_t>::max();
return static_cast<std::uint64_t>(zatoshis);
}
bool modelHasAnySection(const LiteWalletAppRefreshModel& model)
{
return model.hasChainInfo || model.hasHeight || model.hasBalance || model.hasAddresses ||
model.hasSpendableOutputs || model.hasTransactions || model.hasSyncStatus;
}
void addIssue(LiteWalletStateApplyPlan& plan,
LiteWalletStateApplyIssue issue,
std::string message)
{
plan.issues.push_back(LiteWalletStateApplyIssueInfo{issue, std::move(message)});
}
void addFieldPlan(LiteWalletStateApplyPlan& plan,
LiteWalletStateApplySection section,
const std::string& fieldName,
std::string currentValue,
std::string proposedValue)
{
LiteWalletStateApplyFieldPlan fieldPlan;
fieldPlan.section = section;
fieldPlan.fieldName = fieldName;
fieldPlan.currentValue = std::move(currentValue);
fieldPlan.proposedValue = std::move(proposedValue);
fieldPlan.wouldChange = fieldPlan.currentValue != fieldPlan.proposedValue;
fieldPlan.action = fieldPlan.wouldChange
? LiteWalletStateApplyAction::SetField
: LiteWalletStateApplyAction::Noop;
if (fieldPlan.wouldChange) plan.wouldChangeWalletState = true;
plan.fieldPlans.push_back(std::move(fieldPlan));
}
void addCollectionPlan(LiteWalletStateApplyPlan& plan,
LiteWalletStateApplySection section,
std::string collectionName,
std::size_t currentCount,
std::size_t proposedCount,
bool hasWalletStateTarget,
bool wouldReplace)
{
LiteWalletStateApplyCollectionPlan collectionPlan;
collectionPlan.section = section;
collectionPlan.collectionName = std::move(collectionName);
collectionPlan.currentCount = currentCount;
collectionPlan.proposedCount = proposedCount;
collectionPlan.hasWalletStateTarget = hasWalletStateTarget;
collectionPlan.wouldReplace = hasWalletStateTarget && wouldReplace;
if (!hasWalletStateTarget) {
collectionPlan.action = LiteWalletStateApplyAction::InspectOnly;
} else {
collectionPlan.action = collectionPlan.wouldReplace
? LiteWalletStateApplyAction::ReplaceCollection
: LiteWalletStateApplyAction::Noop;
}
if (collectionPlan.wouldReplace) plan.wouldChangeWalletState = true;
plan.collectionPlans.push_back(std::move(collectionPlan));
}
std::vector<std::string> mappedAddresses(const LiteWalletAppRefreshModel& model,
LiteWalletAppAddressKind kind)
{
std::vector<std::string> addresses;
for (const auto& address : model.addresses) {
if (address.kind == kind) addresses.push_back(address.address);
}
return addresses;
}
std::vector<std::string> mappedAddressStrings(const LiteWalletAppRefreshModel& model)
{
std::vector<std::string> addresses;
addresses.reserve(model.addresses.size());
for (const auto& address : model.addresses) addresses.push_back(address.address);
return addresses;
}
std::vector<std::string> stateAddressStrings(const std::vector<dragonx::AddressInfo>& addresses)
{
std::vector<std::string> values;
values.reserve(addresses.size());
for (const auto& address : addresses) values.push_back(address.address);
return values;
}
std::vector<std::string> mappedTransactionIds(const LiteWalletAppRefreshModel& model)
{
std::vector<std::string> txids;
txids.reserve(model.transactions.size());
for (const auto& transaction : model.transactions) txids.push_back(transaction.txid);
return txids;
}
std::vector<std::string> stateTransactionIds(const std::vector<dragonx::TransactionInfo>& transactions)
{
std::vector<std::string> txids;
txids.reserve(transactions.size());
for (const auto& transaction : transactions) txids.push_back(transaction.txid);
return txids;
}
void addChainInfoPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasChainInfo) return;
plan.hasChainInfo = true;
if (model.chain.longestChain) {
addFieldPlan(plan,
LiteWalletStateApplySection::ChainInfo,
"longestchain",
intValue(state.longestchain),
intValue(*model.chain.longestChain));
}
if (model.chain.notarized) {
addFieldPlan(plan,
LiteWalletStateApplySection::ChainInfo,
"notarized",
intValue(state.notarized),
intValue(*model.chain.notarized));
}
if (model.chain.chainName) {
addFieldPlan(plan,
LiteWalletStateApplySection::ChainInfo,
"chain_name",
stringValue(state.mining.chain),
stringValue(*model.chain.chainName));
}
}
void addHeightPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasHeight) return;
plan.hasHeight = true;
addFieldPlan(plan,
LiteWalletStateApplySection::Height,
"sync.blocks",
intValue(state.sync.blocks),
intValue(model.height.height));
}
void addBalancePlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasBalance) return;
plan.hasBalance = true;
addFieldPlan(plan,
LiteWalletStateApplySection::Balance,
"privateBalanceZatoshis",
uintValue(coinsToZatoshis(state.privateBalance)),
uintValue(model.balance.shieldedZatoshis));
addFieldPlan(plan,
LiteWalletStateApplySection::Balance,
"transparentBalanceZatoshis",
uintValue(coinsToZatoshis(state.transparentBalance)),
uintValue(model.balance.transparentZatoshis));
addFieldPlan(plan,
LiteWalletStateApplySection::Balance,
"totalBalanceZatoshis",
uintValue(coinsToZatoshis(state.totalBalance)),
uintValue(model.balance.totalZatoshis));
addFieldPlan(plan,
LiteWalletStateApplySection::Balance,
"unconfirmedBalanceZatoshis",
uintValue(coinsToZatoshis(state.unconfirmedBalance)),
uintValue(model.balance.unconfirmedZatoshis));
}
void addAddressPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasAddresses) return;
plan.hasAddresses = true;
const auto proposedShielded = mappedAddresses(model, LiteWalletAppAddressKind::Shielded);
const auto proposedTransparent = mappedAddresses(model, LiteWalletAppAddressKind::Transparent);
const auto proposedCombined = mappedAddressStrings(model);
const auto currentShielded = stateAddressStrings(state.z_addresses);
const auto currentTransparent = stateAddressStrings(state.t_addresses);
const auto currentCombined = stateAddressStrings(state.addresses);
addCollectionPlan(plan,
LiteWalletStateApplySection::Addresses,
"z_addresses",
currentShielded.size(),
proposedShielded.size(),
true,
currentShielded != proposedShielded);
addCollectionPlan(plan,
LiteWalletStateApplySection::Addresses,
"t_addresses",
currentTransparent.size(),
proposedTransparent.size(),
true,
currentTransparent != proposedTransparent);
addCollectionPlan(plan,
LiteWalletStateApplySection::Addresses,
"addresses",
currentCombined.size(),
proposedCombined.size(),
true,
currentCombined != proposedCombined);
const auto unknownSpendability = std::any_of(model.addresses.begin(), model.addresses.end(),
[](const LiteWalletAppAddressModel& address) { return !address.spendabilityKnown; });
if (unknownSpendability) {
addIssue(plan,
LiteWalletStateApplyIssue::AddressSpendabilityUnknown,
"lite address rows do not yet carry wallet spendability policy");
}
}
void addSpendableOutputPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model)
{
if (!model.hasSpendableOutputs) return;
plan.hasSpendableOutputs = true;
addCollectionPlan(plan,
LiteWalletStateApplySection::SpendableOutputs,
"spendableOutputs",
0,
model.spendableOutputs.size(),
false,
false);
addIssue(plan,
LiteWalletStateApplyIssue::SpendableOutputsHaveNoWalletStateTarget,
"lite spendable outputs remain source-only until an app apply target is designed");
}
void addTransactionPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasTransactions) return;
plan.hasTransactions = true;
const auto proposedTxids = mappedTransactionIds(model);
const auto currentTxids = stateTransactionIds(state.transactions);
addCollectionPlan(plan,
LiteWalletStateApplySection::Transactions,
"transactions",
currentTxids.size(),
proposedTxids.size(),
true,
currentTxids != proposedTxids);
}
void addSyncPlans(LiteWalletStateApplyPlan& plan,
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
if (!model.hasSyncStatus) return;
plan.hasSyncStatus = true;
addFieldPlan(plan,
LiteWalletStateApplySection::SyncStatus,
"sync.blocks",
intValue(state.sync.blocks),
uintValue(model.sync.walletHeight));
addFieldPlan(plan,
LiteWalletStateApplySection::SyncStatus,
"sync.headers",
intValue(state.sync.headers),
uintValue(model.sync.chainHeight));
addFieldPlan(plan,
LiteWalletStateApplySection::SyncStatus,
"sync.verification_progress",
doubleValue(state.sync.verification_progress),
doubleValue(model.sync.progress));
addFieldPlan(plan,
LiteWalletStateApplySection::SyncStatus,
"sync.syncing",
boolValue(state.sync.syncing),
boolValue(!model.sync.complete));
}
} // namespace
const char* liteWalletStateApplySectionName(LiteWalletStateApplySection section)
{
switch (section) {
case LiteWalletStateApplySection::ChainInfo: return "ChainInfo";
case LiteWalletStateApplySection::Height: return "Height";
case LiteWalletStateApplySection::Balance: return "Balance";
case LiteWalletStateApplySection::Addresses: return "Addresses";
case LiteWalletStateApplySection::SpendableOutputs: return "SpendableOutputs";
case LiteWalletStateApplySection::Transactions: return "Transactions";
case LiteWalletStateApplySection::SyncStatus: return "SyncStatus";
}
return "Unknown";
}
const char* liteWalletStateApplyActionName(LiteWalletStateApplyAction action)
{
switch (action) {
case LiteWalletStateApplyAction::Noop: return "Noop";
case LiteWalletStateApplyAction::SetField: return "SetField";
case LiteWalletStateApplyAction::ReplaceCollection: return "ReplaceCollection";
case LiteWalletStateApplyAction::InspectOnly: return "InspectOnly";
}
return "Unknown";
}
const char* liteWalletStateApplyIssueName(LiteWalletStateApplyIssue issue)
{
switch (issue) {
case LiteWalletStateApplyIssue::EmptyModel: return "EmptyModel";
case LiteWalletStateApplyIssue::IncompleteModel: return "IncompleteModel";
case LiteWalletStateApplyIssue::AddressSpendabilityUnknown: return "AddressSpendabilityUnknown";
case LiteWalletStateApplyIssue::SpendableOutputsHaveNoWalletStateTarget: return "SpendableOutputsHaveNoWalletStateTarget";
}
return "Unknown";
}
LiteWalletStateApplyPlan planLiteWalletStateApply(const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state)
{
LiteWalletStateApplyPlan plan;
plan.sourceComplete = model.complete;
if (!modelHasAnySection(model)) {
addIssue(plan,
LiteWalletStateApplyIssue::EmptyModel,
"lite app refresh model has no sections to compare");
plan.error = plan.issues.back().message;
return plan;
}
if (!model.complete) {
addIssue(plan,
LiteWalletStateApplyIssue::IncompleteModel,
"lite app refresh model is partial; dry-run apply planning only");
}
addChainInfoPlans(plan, model, state);
addHeightPlans(plan, model, state);
addBalancePlans(plan, model, state);
addAddressPlans(plan, model, state);
addSpendableOutputPlans(plan, model);
addTransactionPlans(plan, model, state);
addSyncPlans(plan, model, state);
plan.ok = true;
return plan;
}
LiteWalletStateApplyPlan LiteWalletStateApplyPlanner::buildPlan(
const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state) const
{
return planLiteWalletStateApply(model, state);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,98 @@
#pragma once
#include "data/wallet_state.h"
#include "lite_wallet_state_mapper.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletStateApplySection {
ChainInfo,
Height,
Balance,
Addresses,
SpendableOutputs,
Transactions,
SyncStatus,
};
enum class LiteWalletStateApplyAction {
Noop,
SetField,
ReplaceCollection,
InspectOnly,
};
enum class LiteWalletStateApplyIssue {
EmptyModel,
IncompleteModel,
AddressSpendabilityUnknown,
SpendableOutputsHaveNoWalletStateTarget,
};
struct LiteWalletStateApplyIssueInfo {
LiteWalletStateApplyIssue issue = LiteWalletStateApplyIssue::EmptyModel;
std::string message;
};
struct LiteWalletStateApplyFieldPlan {
LiteWalletStateApplySection section = LiteWalletStateApplySection::Balance;
LiteWalletStateApplyAction action = LiteWalletStateApplyAction::Noop;
std::string fieldName;
std::string currentValue;
std::string proposedValue;
bool currentKnown = true;
bool proposedKnown = true;
bool wouldChange = false;
};
struct LiteWalletStateApplyCollectionPlan {
LiteWalletStateApplySection section = LiteWalletStateApplySection::Addresses;
LiteWalletStateApplyAction action = LiteWalletStateApplyAction::Noop;
std::string collectionName;
std::size_t currentCount = 0;
std::size_t proposedCount = 0;
bool hasWalletStateTarget = true;
bool wouldReplace = false;
};
struct LiteWalletStateApplyPlan {
bool ok = false;
bool dryRunOnly = true;
bool applyImplemented = false;
bool stateMutationAllowed = false;
bool wouldChangeWalletState = false;
bool sourceComplete = false;
bool hasChainInfo = false;
bool hasHeight = false;
bool hasBalance = false;
bool hasAddresses = false;
bool hasSpendableOutputs = false;
bool hasTransactions = false;
bool hasSyncStatus = false;
std::vector<LiteWalletStateApplyFieldPlan> fieldPlans;
std::vector<LiteWalletStateApplyCollectionPlan> collectionPlans;
std::vector<LiteWalletStateApplyIssueInfo> issues;
std::string error;
};
const char* liteWalletStateApplySectionName(LiteWalletStateApplySection section);
const char* liteWalletStateApplyActionName(LiteWalletStateApplyAction action);
const char* liteWalletStateApplyIssueName(LiteWalletStateApplyIssue issue);
LiteWalletStateApplyPlan planLiteWalletStateApply(const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state);
class LiteWalletStateApplyPlanner {
public:
LiteWalletStateApplyPlan buildPlan(const LiteWalletAppRefreshModel& model,
const dragonx::WalletState& state) const;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,217 @@
#include "wallet/lite_wallet_state_mapper.h"
#include <utility>
namespace dragonx::wallet {
namespace {
LiteWalletAppAddressModel mapAddress(const std::string& address, LiteWalletAppAddressKind kind)
{
LiteWalletAppAddressModel model;
model.address = address;
model.kind = kind;
return model;
}
LiteWalletAppSpendableOutputModel mapSpendableOutput(const LiteSpendableOutput& output)
{
LiteWalletAppSpendableOutputModel model;
model.kind = output.kind;
model.address = output.address;
model.createdInTxid = output.createdInTxid;
model.createdInBlock = output.createdInBlock;
model.valueZatoshis = output.value;
model.spent = output.spent;
model.unconfirmedSpent = output.unconfirmedSpent;
model.pending = output.pending;
model.spendable = output.spendable;
return model;
}
LiteWalletAppTransactionKind mapTransactionKind(LiteTransactionDirection direction)
{
switch (direction) {
case LiteTransactionDirection::Send: return LiteWalletAppTransactionKind::Send;
case LiteTransactionDirection::Receive: return LiteWalletAppTransactionKind::Receive;
case LiteTransactionDirection::Unknown: return LiteWalletAppTransactionKind::Unknown;
}
return LiteWalletAppTransactionKind::Unknown;
}
LiteWalletAppTransactionOutputModel mapTransactionOutput(const LiteTransactionOutput& output)
{
LiteWalletAppTransactionOutputModel model;
model.address = output.address;
model.valueZatoshis = output.value;
model.memo = output.memo;
return model;
}
LiteWalletAppTransactionModel mapTransaction(const LiteTransactionRecord& transaction)
{
LiteWalletAppTransactionModel model;
model.txid = transaction.txid;
model.kind = mapTransactionKind(transaction.direction);
model.timestamp = transaction.datetime;
model.blockHeight = transaction.blockHeight;
model.unconfirmed = transaction.unconfirmed;
model.address = transaction.address;
model.amountZatoshis = transaction.amount;
model.signedAmountZatoshis = model.kind == LiteWalletAppTransactionKind::Send
? -transaction.amount
: transaction.amount;
model.memo = transaction.memo;
model.position = transaction.position;
model.outgoingOutputs.reserve(transaction.outgoingMetadata.size());
for (const auto& output : transaction.outgoingMetadata) {
model.outgoingOutputs.push_back(mapTransactionOutput(output));
}
return model;
}
bool modelHasAnyMappedField(const LiteWalletAppRefreshModel& model)
{
return model.hasChainInfo || model.hasHeight || model.hasBalance || model.hasAddresses ||
model.hasSpendableOutputs || model.hasTransactions || model.hasSyncStatus;
}
void addIssue(LiteWalletStateMapResult& result, LiteWalletStateMapIssue issue, std::string message)
{
result.issues.push_back(LiteWalletStateMapIssueInfo{issue, std::move(message)});
}
} // namespace
const char* liteWalletAppAddressKindName(LiteWalletAppAddressKind kind)
{
switch (kind) {
case LiteWalletAppAddressKind::Shielded: return "shielded";
case LiteWalletAppAddressKind::Transparent: return "transparent";
}
return "unknown";
}
const char* liteWalletAppTransactionKindName(LiteWalletAppTransactionKind kind)
{
switch (kind) {
case LiteWalletAppTransactionKind::Unknown: return "unknown";
case LiteWalletAppTransactionKind::Send: return "send";
case LiteWalletAppTransactionKind::Receive: return "receive";
}
return "unknown";
}
const char* liteWalletStateMapIssueName(LiteWalletStateMapIssue issue)
{
switch (issue) {
case LiteWalletStateMapIssue::EmptyBundle: return "EmptyBundle";
case LiteWalletStateMapIssue::IncompleteBundle: return "IncompleteBundle";
}
return "Unknown";
}
LiteWalletStateMapResult mapLiteWalletRefreshBundle(const LiteWalletRefreshBundle& bundle)
{
LiteWalletStateMapResult result;
result.stateMutationAllowed = false;
result.model.complete = bundle.complete;
result.model.successfulCommandCount = bundle.successfulCommandCount;
if (bundle.hasInfo) {
result.model.hasChainInfo = true;
result.model.chain.chainName = bundle.info.chainName;
result.model.chain.version = bundle.info.version;
result.model.chain.vendor = bundle.info.vendor;
result.model.chain.latestBlockHeight = bundle.info.latestBlockHeight;
result.model.chain.difficulty = bundle.info.difficulty;
result.model.chain.longestChain = bundle.info.longestChain;
result.model.chain.notarized = bundle.info.notarized;
}
if (bundle.hasHeight) {
result.model.hasHeight = true;
result.model.height.height = bundle.height.height;
}
if (bundle.hasBalance) {
result.model.hasBalance = true;
result.model.balance.transparentZatoshis = bundle.balance.transparentBalance;
result.model.balance.shieldedZatoshis = bundle.balance.shieldedBalance;
result.model.balance.unconfirmedZatoshis = bundle.balance.unconfirmedBalance;
result.model.balance.verifiedShieldedZatoshis = bundle.balance.verifiedShieldedBalance;
result.model.balance.spendableShieldedZatoshis = bundle.balance.spendableShieldedBalance;
result.model.balance.totalZatoshis = bundle.balance.transparentBalance + bundle.balance.shieldedBalance;
}
if (bundle.hasAddresses) {
result.model.hasAddresses = true;
result.model.addresses.reserve(bundle.addresses.zAddresses.size() + bundle.addresses.tAddresses.size());
for (const auto& address : bundle.addresses.zAddresses) {
result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Shielded));
}
for (const auto& address : bundle.addresses.tAddresses) {
result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Transparent));
}
}
if (bundle.hasNotes) {
result.model.hasSpendableOutputs = true;
const auto outputCount = bundle.notes.unspentNotes.size() + bundle.notes.utxos.size() +
bundle.notes.pendingNotes.size() + bundle.notes.pendingUtxos.size();
result.model.spendableOutputs.reserve(outputCount);
for (const auto& output : bundle.notes.unspentNotes) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.utxos) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.pendingNotes) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.pendingUtxos) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
}
if (bundle.hasTransactions) {
result.model.hasTransactions = true;
result.model.transactions.reserve(bundle.transactions.transactions.size());
for (const auto& transaction : bundle.transactions.transactions) {
result.model.transactions.push_back(mapTransaction(transaction));
}
}
if (bundle.hasSyncStatus) {
result.model.hasSyncStatus = true;
result.model.sync.walletHeight = bundle.syncStatus.syncedBlocks;
result.model.sync.chainHeight = bundle.syncStatus.totalBlocks;
result.model.sync.progress = bundle.syncStatus.progress;
result.model.sync.complete = bundle.syncStatus.complete;
}
if (!modelHasAnyMappedField(result.model)) {
addIssue(result, LiteWalletStateMapIssue::EmptyBundle, "lite refresh bundle has no mappable fields");
result.error = result.issues.back().message;
return result;
}
if (!bundle.complete) {
addIssue(result, LiteWalletStateMapIssue::IncompleteBundle, "lite refresh bundle is partial");
}
result.ok = true;
return result;
}
LiteWalletStateMapResult mapLiteWalletRefreshResult(const LiteWalletRefreshResult& result)
{
return mapLiteWalletRefreshBundle(result.bundle);
}
LiteWalletStateMapResult mapLiteWalletRefreshServiceResult(const LiteWalletRefreshServiceResult& result)
{
return mapLiteWalletRefreshBundle(result.bundle);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,139 @@
#pragma once
#include "lite_wallet_refresh_service.h"
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletAppAddressKind {
Shielded,
Transparent,
};
enum class LiteWalletAppTransactionKind {
Unknown,
Send,
Receive,
};
enum class LiteWalletStateMapIssue {
EmptyBundle,
IncompleteBundle,
};
struct LiteWalletAppChainModel {
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 LiteWalletAppHeightModel {
std::int64_t height = 0;
};
struct LiteWalletAppBalanceModel {
std::uint64_t transparentZatoshis = 0;
std::uint64_t shieldedZatoshis = 0;
std::uint64_t unconfirmedZatoshis = 0;
std::uint64_t verifiedShieldedZatoshis = 0;
std::uint64_t spendableShieldedZatoshis = 0;
std::uint64_t totalZatoshis = 0;
};
struct LiteWalletAppAddressModel {
std::string address;
LiteWalletAppAddressKind kind = LiteWalletAppAddressKind::Shielded;
bool spendabilityKnown = false;
bool spendable = false;
};
struct LiteWalletAppSpendableOutputModel {
LiteSpendableOutputKind kind = LiteSpendableOutputKind::UnspentNote;
std::string address;
std::string createdInTxid;
std::optional<std::int64_t> createdInBlock;
std::uint64_t valueZatoshis = 0;
bool spent = false;
bool unconfirmedSpent = false;
bool pending = false;
bool spendable = false;
};
struct LiteWalletAppTransactionOutputModel {
std::string address;
std::int64_t valueZatoshis = 0;
std::string memo;
};
struct LiteWalletAppTransactionModel {
std::string txid;
LiteWalletAppTransactionKind kind = LiteWalletAppTransactionKind::Unknown;
std::int64_t timestamp = 0;
std::optional<std::int64_t> blockHeight;
bool unconfirmed = false;
std::string address;
std::int64_t amountZatoshis = 0;
std::int64_t signedAmountZatoshis = 0;
std::string memo;
std::optional<std::int64_t> position;
std::vector<LiteWalletAppTransactionOutputModel> outgoingOutputs;
};
struct LiteWalletAppSyncModel {
std::uint64_t walletHeight = 0;
std::uint64_t chainHeight = 0;
double progress = 0.0;
bool complete = false;
};
struct LiteWalletAppRefreshModel {
bool complete = false;
bool hasChainInfo = false;
bool hasHeight = false;
bool hasBalance = false;
bool hasAddresses = false;
bool hasSpendableOutputs = false;
bool hasTransactions = false;
bool hasSyncStatus = false;
std::size_t successfulCommandCount = 0;
LiteWalletAppChainModel chain;
LiteWalletAppHeightModel height;
LiteWalletAppBalanceModel balance;
LiteWalletAppSyncModel sync;
std::vector<LiteWalletAppAddressModel> addresses;
std::vector<LiteWalletAppSpendableOutputModel> spendableOutputs;
std::vector<LiteWalletAppTransactionModel> transactions;
};
struct LiteWalletStateMapIssueInfo {
LiteWalletStateMapIssue issue = LiteWalletStateMapIssue::EmptyBundle;
std::string message;
};
struct LiteWalletStateMapResult {
bool ok = false;
bool stateMutationAllowed = false;
LiteWalletAppRefreshModel model;
std::vector<LiteWalletStateMapIssueInfo> issues;
std::string error;
};
const char* liteWalletAppAddressKindName(LiteWalletAppAddressKind kind);
const char* liteWalletAppTransactionKindName(LiteWalletAppTransactionKind kind);
const char* liteWalletStateMapIssueName(LiteWalletStateMapIssue issue);
LiteWalletStateMapResult mapLiteWalletRefreshBundle(const LiteWalletRefreshBundle& bundle);
LiteWalletStateMapResult mapLiteWalletRefreshResult(const LiteWalletRefreshResult& result);
LiteWalletStateMapResult mapLiteWalletRefreshServiceResult(const LiteWalletRefreshServiceResult& result);
} // namespace dragonx::wallet

View File

@@ -0,0 +1,344 @@
#include "wallet/lite_wallet_sync_app_refresh_integration.h"
#include <utility>
namespace dragonx::wallet {
namespace {
void addIssue(LiteWalletSyncAppRefreshIntegrationResult& result,
LiteWalletSyncAppRefreshIntegrationIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletSyncAppRefreshIntegrationIssueInfo{issue, std::move(message)});
}
std::string statusMessageOrDefault(const WalletBackendStatus& status,
const std::string& fallback)
{
return status.message.empty() ? fallback : status.message;
}
bool refreshReportIsUnsafe(const LiteWalletAppRefreshOrchestrationResult& refresh)
{
return !refresh.dryRunOnly ||
!refresh.noNetwork ||
refresh.stateMutationRequested ||
refresh.stateMutationAllowed ||
refresh.stateMutated ||
refresh.walletStateWritten;
}
bool syncStartPlanIsValid(const LiteSyncPlan& plan)
{
return plan.ok && plan.operation == LiteSyncOperation::StartSync && plan.command == "sync";
}
bool syncStatusPlanIsValid(const LiteSyncPlan& plan)
{
return plan.ok && plan.operation == LiteSyncOperation::PollSyncStatus && plan.command == "syncstatus";
}
LiteWalletSyncAppRefreshIntegrationResult stoppedResult(
LiteWalletSyncAppRefreshIntegrationResult result,
LiteWalletSyncAppRefreshIntegrationStatus status,
LiteWalletSyncAppRefreshIntegrationIssue issue,
std::string message)
{
result.status = status;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
return result;
}
LiteWalletSyncAppRefreshIntegrationResult skippedResult(
LiteWalletSyncAppRefreshIntegrationResult result,
LiteWalletSyncAppRefreshIntegrationIssue issue,
std::string message)
{
result.ok = true;
result.status = LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued;
addIssue(result, issue, std::move(message));
return result;
}
void copyRecoveryFlags(LiteWalletSyncAppRefreshIntegrationResult& result,
const LiteSyncRecoveryDecision& decision)
{
result.recoveryDecision = decision;
result.futureClearRequired = decision.shouldClear;
result.futureRescanRequired = decision.shouldRescan;
result.futureRestartSyncRequired = decision.shouldRestartSync;
result.requiresUserAttention = decision.requiresUserAttention;
}
} // namespace
const char* liteWalletSyncAppRefreshIntegrationStatusName(
LiteWalletSyncAppRefreshIntegrationStatus status)
{
switch (status) {
case LiteWalletSyncAppRefreshIntegrationStatus::FutureWorkerQueueFeedReady: return "FutureWorkerQueueFeedReady";
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForLifecycle: return "WaitingForLifecycle";
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan: return "WaitingForSyncPlan";
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus: return "WaitingForSyncStatus";
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery: return "WaitingForRecovery";
case LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation: return "WaitingForCancellation";
case LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued: return "RefreshNotQueued";
case LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable: return "WorkerQueueUnavailable";
case LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan: return "UnsafePlan";
}
return "Unknown";
}
const char* liteWalletSyncAppRefreshIntegrationIssueName(
LiteWalletSyncAppRefreshIntegrationIssue issue)
{
switch (issue) {
case LiteWalletSyncAppRefreshIntegrationIssue::LifecycleNotReady: return "LifecycleNotReady";
case LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStartPlan: return "MissingSyncStartPlan";
case LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStartPlan: return "InvalidSyncStartPlan";
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStartWouldExecute: return "SyncStartWouldExecute";
case LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStatusPlan: return "MissingSyncStatusPlan";
case LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStatusPlan: return "InvalidSyncStatusPlan";
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStatusWouldExecute: return "SyncStatusWouldExecute";
case LiteWalletSyncAppRefreshIntegrationIssue::MissingRecoveryDecision: return "MissingRecoveryDecision";
case LiteWalletSyncAppRefreshIntegrationIssue::SyncStillPolling: return "SyncStillPolling";
case LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryRequired: return "SyncRecoveryRequired";
case LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryNeedsUserAttention: return "SyncRecoveryNeedsUserAttention";
case LiteWalletSyncAppRefreshIntegrationIssue::CancellationRequired: return "CancellationRequired";
case LiteWalletSyncAppRefreshIntegrationIssue::CancellationUnsupported: return "CancellationUnsupported";
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportRejected: return "RefreshReportRejected";
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportUnsafe: return "RefreshReportUnsafe";
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshSkipped: return "RefreshSkipped";
case LiteWalletSyncAppRefreshIntegrationIssue::RefreshBlocked: return "RefreshBlocked";
case LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueueDisabled: return "FutureWorkerQueueDisabled";
case LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueuePressure: return "FutureWorkerQueuePressure";
}
return "Unknown";
}
LiteWalletSyncAppRefreshIntegrationResult planLiteWalletSyncAppRefreshIntegration(
const LiteWalletSyncAppRefreshIntegrationInput& input,
LiteWalletSyncAppRefreshIntegrationOptions options)
{
LiteWalletSyncAppRefreshIntegrationResult result;
result.lifecycleStatus = input.lifecycle.status;
result.refreshOrchestration = input.refreshOrchestration;
result.route = input.refreshOrchestration.route;
result.refreshWouldQueue = input.refreshOrchestration.wouldQueueRefresh;
result.refreshSkipped = input.refreshOrchestration.skipped;
result.refreshBlocked = input.refreshOrchestration.blocked;
result.futureWorkerQueuePlan.laneName = input.futureWorkerQueue.laneName;
result.futureWorkerQueuePlan.trigger = input.refreshOrchestration.trigger;
result.futureWorkerQueuePlan.route = input.refreshOrchestration.route;
result.futureWorkerQueuePlan.queueDepth = input.futureWorkerQueue.queueDepth;
result.futureWorkerQueuePlan.maxQueueDepth = input.futureWorkerQueue.maxQueueDepth;
if (input.sync.haveStartPlan) {
result.syncStartPlan = input.sync.startPlan;
result.futureWorkerQueuePlan.startSyncCommand = input.sync.startPlan.command;
}
if (input.sync.haveStatusPlan) {
result.syncStatusPlan = input.sync.statusPlan;
result.futureWorkerQueuePlan.syncStatusCommand = input.sync.statusPlan.command;
}
if (input.sync.haveRecoveryDecision) {
copyRecoveryFlags(result, input.sync.recoveryDecision);
}
if (options.requireLifecycleReady && !input.lifecycle.ready) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForLifecycle,
LiteWalletSyncAppRefreshIntegrationIssue::LifecycleNotReady,
statusMessageOrDefault(input.lifecycle.status, "lite wallet lifecycle is not ready"));
}
result.lifecycleAccepted = true;
if (options.requireSyncStartPlan && !input.sync.haveStartPlan) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan,
LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStartPlan,
"lite sync start plan is required before app refresh integration");
}
if (input.sync.haveStartPlan) {
if (!syncStartPlanIsValid(input.sync.startPlan)) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncPlan,
LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStartPlan,
input.sync.startPlan.error.empty()
? "lite sync start plan is invalid"
: input.sync.startPlan.error);
}
if (options.rejectExecutableSyncPlans && input.sync.startPlan.bridgeExecutionAllowed) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
LiteWalletSyncAppRefreshIntegrationIssue::SyncStartWouldExecute,
"lite sync start plan would allow bridge execution");
}
result.syncStartPlanAccepted = true;
}
if (options.requireSyncStatusPlan && !input.sync.haveStatusPlan) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
LiteWalletSyncAppRefreshIntegrationIssue::MissingSyncStatusPlan,
"lite syncstatus plan is required before app refresh integration");
}
if (input.sync.haveStatusPlan) {
if (!syncStatusPlanIsValid(input.sync.statusPlan)) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
LiteWalletSyncAppRefreshIntegrationIssue::InvalidSyncStatusPlan,
input.sync.statusPlan.error.empty()
? "lite syncstatus plan is invalid"
: input.sync.statusPlan.error);
}
if (options.rejectExecutableSyncPlans && input.sync.statusPlan.bridgeExecutionAllowed) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
LiteWalletSyncAppRefreshIntegrationIssue::SyncStatusWouldExecute,
"lite syncstatus plan would allow bridge execution");
}
result.syncStatusPlanAccepted = true;
}
if (options.requireRecoveryDecision && !input.sync.haveRecoveryDecision) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
LiteWalletSyncAppRefreshIntegrationIssue::MissingRecoveryDecision,
"lite sync recovery decision is required before app refresh integration");
}
if (input.sync.haveRecoveryDecision) {
const auto& decision = input.sync.recoveryDecision;
if (decision.kind == LiteSyncRecoveryDecisionKind::InvalidStatus) {
result.recoveryRequired = true;
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery,
LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryNeedsUserAttention,
decision.reason.empty()
? "lite sync status is invalid and needs user attention"
: decision.reason);
}
if (decision.kind == LiteSyncRecoveryDecisionKind::Stuck ||
decision.kind == LiteSyncRecoveryDecisionKind::ReorgDetected) {
result.recoveryRequired = true;
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForRecovery,
LiteWalletSyncAppRefreshIntegrationIssue::SyncRecoveryRequired,
decision.reason.empty()
? "lite sync recovery must be modeled before refresh can feed a queue"
: decision.reason);
}
if (options.requireSyncCompleteForRefresh && decision.kind == LiteSyncRecoveryDecisionKind::KeepPolling) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForSyncStatus,
LiteWalletSyncAppRefreshIntegrationIssue::SyncStillPolling,
decision.reason.empty()
? "lite sync is still polling"
: decision.reason);
}
result.recoveryAccepted = true;
}
if (input.cancellation.cancellationRequested && input.cancellation.syncInProgress) {
result.cancellationRequired = true;
if (!input.cancellation.cancellationSupported) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation,
LiteWalletSyncAppRefreshIntegrationIssue::CancellationUnsupported,
"lite sync cancellation is requested but no cancellation path is available");
}
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation,
LiteWalletSyncAppRefreshIntegrationIssue::CancellationRequired,
"lite sync cancellation must complete before app refresh can feed a queue");
}
result.cancellationAccepted = true;
if (refreshReportIsUnsafe(input.refreshOrchestration)) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::UnsafePlan,
LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportUnsafe,
"lite app refresh orchestration report is not a no-network dry-run report");
}
if (!input.refreshOrchestration.ok) {
const auto issue = input.refreshOrchestration.blocked
? LiteWalletSyncAppRefreshIntegrationIssue::RefreshBlocked
: LiteWalletSyncAppRefreshIntegrationIssue::RefreshReportRejected;
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::RefreshNotQueued,
issue,
input.refreshOrchestration.error.empty()
? "lite app refresh orchestration report is not queued"
: input.refreshOrchestration.error);
}
result.refreshReportAccepted = true;
if (input.refreshOrchestration.skipped || !input.refreshOrchestration.wouldQueueRefresh) {
return skippedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationIssue::RefreshSkipped,
input.refreshOrchestration.issues.empty()
? "lite app refresh orchestration skipped queueing"
: input.refreshOrchestration.issues.front().message);
}
if (options.requireFutureWorkerQueue && !input.futureWorkerQueue.enabled) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable,
LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueueDisabled,
"future lite app refresh worker queue is disabled");
}
if (input.futureWorkerQueue.maxQueueDepth > 0 &&
input.futureWorkerQueue.queueDepth >= input.futureWorkerQueue.maxQueueDepth) {
return stoppedResult(
std::move(result),
LiteWalletSyncAppRefreshIntegrationStatus::WorkerQueueUnavailable,
LiteWalletSyncAppRefreshIntegrationIssue::FutureWorkerQueuePressure,
"future lite app refresh worker queue is at capacity");
}
result.ok = true;
result.status = LiteWalletSyncAppRefreshIntegrationStatus::FutureWorkerQueueFeedReady;
result.futureWorkerQueueFeedReady = true;
result.wouldFeedFutureWorkerQueue = true;
result.futureWorkerQueuePlan.readyToFeed = true;
result.futureWorkerQueuePlan.wouldFeed = true;
return result;
}
LiteWalletSyncAppRefreshIntegrationPlanner::LiteWalletSyncAppRefreshIntegrationPlanner(
LiteWalletSyncAppRefreshIntegrationOptions options)
: options_(options)
{
}
LiteWalletSyncAppRefreshIntegrationResult LiteWalletSyncAppRefreshIntegrationPlanner::plan(
const LiteWalletSyncAppRefreshIntegrationInput& input) const
{
return planLiteWalletSyncAppRefreshIntegration(input, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,172 @@
#pragma once
#include "lite_sync_service.h"
#include "lite_wallet_app_refresh_orchestrator.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletSyncAppRefreshIntegrationStatus {
FutureWorkerQueueFeedReady,
WaitingForLifecycle,
WaitingForSyncPlan,
WaitingForSyncStatus,
WaitingForRecovery,
WaitingForCancellation,
RefreshNotQueued,
WorkerQueueUnavailable,
UnsafePlan,
};
enum class LiteWalletSyncAppRefreshIntegrationIssue {
LifecycleNotReady,
MissingSyncStartPlan,
InvalidSyncStartPlan,
SyncStartWouldExecute,
MissingSyncStatusPlan,
InvalidSyncStatusPlan,
SyncStatusWouldExecute,
MissingRecoveryDecision,
SyncStillPolling,
SyncRecoveryRequired,
SyncRecoveryNeedsUserAttention,
CancellationRequired,
CancellationUnsupported,
RefreshReportRejected,
RefreshReportUnsafe,
RefreshSkipped,
RefreshBlocked,
FutureWorkerQueueDisabled,
FutureWorkerQueuePressure,
};
struct LiteWalletSyncAppRefreshLifecycleInput {
bool ready = false;
WalletBackendStatus status;
};
struct LiteWalletSyncAppRefreshSyncPlanInput {
bool haveStartPlan = false;
LiteSyncPlan startPlan;
bool haveStatusPlan = false;
LiteSyncPlan statusPlan;
bool haveRecoveryDecision = false;
LiteSyncRecoveryDecision recoveryDecision;
};
struct LiteWalletSyncAppRefreshCancellationInput {
bool cancellationRequested = false;
bool syncInProgress = false;
bool cancellationSupported = false;
};
struct LiteWalletFutureWorkerQueueInput {
bool enabled = true;
std::size_t queueDepth = 0;
std::size_t maxQueueDepth = 0;
std::string laneName = "lite-app-refresh";
};
struct LiteWalletSyncAppRefreshIntegrationInput {
LiteWalletSyncAppRefreshLifecycleInput lifecycle;
LiteWalletSyncAppRefreshSyncPlanInput sync;
LiteWalletSyncAppRefreshCancellationInput cancellation;
LiteWalletFutureWorkerQueueInput futureWorkerQueue;
LiteWalletAppRefreshOrchestrationResult refreshOrchestration;
};
struct LiteWalletSyncAppRefreshIntegrationOptions {
bool requireLifecycleReady = true;
bool requireSyncStartPlan = true;
bool requireSyncStatusPlan = true;
bool requireRecoveryDecision = true;
bool requireSyncCompleteForRefresh = true;
bool rejectExecutableSyncPlans = true;
bool requireFutureWorkerQueue = true;
};
struct LiteWalletFutureWorkerQueuePlan {
bool readyToFeed = false;
bool wouldFeed = false;
bool enqueued = false;
std::string laneName;
LiteWalletAppRefreshScheduleTrigger trigger = LiteWalletAppRefreshScheduleTrigger::Periodic;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
std::string startSyncCommand;
std::string syncStatusCommand;
std::size_t queueDepth = 0;
std::size_t maxQueueDepth = 0;
};
struct LiteWalletSyncAppRefreshIntegrationIssueInfo {
LiteWalletSyncAppRefreshIntegrationIssue issue = LiteWalletSyncAppRefreshIntegrationIssue::LifecycleNotReady;
std::string message;
};
struct LiteWalletSyncAppRefreshIntegrationResult {
bool ok = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool noBridgeCalls = true;
bool noSyncStarted = true;
bool noWalletPersistence = true;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool workerQueueEnqueued = false;
bool lifecycleAccepted = false;
bool syncStartPlanAccepted = false;
bool syncStatusPlanAccepted = false;
bool recoveryAccepted = false;
bool cancellationAccepted = false;
bool refreshReportAccepted = false;
bool futureWorkerQueueFeedReady = false;
bool wouldFeedFutureWorkerQueue = false;
bool refreshWouldQueue = false;
bool refreshSkipped = false;
bool refreshBlocked = false;
bool recoveryRequired = false;
bool cancellationRequired = false;
bool futureClearRequired = false;
bool futureRescanRequired = false;
bool futureRestartSyncRequired = false;
bool requiresUserAttention = false;
LiteWalletSyncAppRefreshIntegrationStatus status = LiteWalletSyncAppRefreshIntegrationStatus::WaitingForLifecycle;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
WalletBackendStatus lifecycleStatus;
LiteSyncPlan syncStartPlan;
LiteSyncPlan syncStatusPlan;
LiteSyncRecoveryDecision recoveryDecision;
LiteWalletAppRefreshOrchestrationResult refreshOrchestration;
LiteWalletFutureWorkerQueuePlan futureWorkerQueuePlan;
std::vector<LiteWalletSyncAppRefreshIntegrationIssueInfo> issues;
std::string error;
};
const char* liteWalletSyncAppRefreshIntegrationStatusName(
LiteWalletSyncAppRefreshIntegrationStatus status);
const char* liteWalletSyncAppRefreshIntegrationIssueName(
LiteWalletSyncAppRefreshIntegrationIssue issue);
LiteWalletSyncAppRefreshIntegrationResult planLiteWalletSyncAppRefreshIntegration(
const LiteWalletSyncAppRefreshIntegrationInput& input,
LiteWalletSyncAppRefreshIntegrationOptions options = {});
class LiteWalletSyncAppRefreshIntegrationPlanner {
public:
explicit LiteWalletSyncAppRefreshIntegrationPlanner(
LiteWalletSyncAppRefreshIntegrationOptions options = {});
LiteWalletSyncAppRefreshIntegrationResult plan(
const LiteWalletSyncAppRefreshIntegrationInput& input) const;
private:
LiteWalletSyncAppRefreshIntegrationOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,461 @@
#include "wallet/lite_wallet_sync_execution_readiness.h"
#include <utility>
namespace dragonx::wallet {
namespace {
void addIssue(LiteWalletSyncExecutionReadinessResult& result,
LiteWalletSyncExecutionReadinessIssue issue,
std::string message)
{
result.issues.push_back(LiteWalletSyncExecutionReadinessIssueInfo{issue, std::move(message)});
}
LiteWalletSyncExecutionReadinessResult stoppedResult(
LiteWalletSyncExecutionReadinessResult result,
LiteWalletSyncExecutionReadinessStatus status,
LiteWalletSyncExecutionReadinessIssue issue,
std::string message)
{
result.status = status;
addIssue(result, issue, std::move(message));
result.error = result.issues.back().message;
return result;
}
bool coreArtifactSymbolsReady(const LiteWalletSdxlArtifactSymbolsInput& symbols)
{
return symbols.walletExists &&
symbols.initializeNew &&
symbols.initializeNewFromPhrase &&
symbols.initializeExisting &&
symbols.execute &&
symbols.checkServerOnline;
}
bool integrationReportIsUnsafe(const LiteWalletSyncAppRefreshIntegrationResult& integration)
{
return !integration.dryRunOnly ||
!integration.noNetwork ||
!integration.noBridgeCalls ||
!integration.noSyncStarted ||
!integration.noWalletPersistence ||
integration.stateMutationAllowed ||
integration.stateMutated ||
integration.walletStateWritten ||
integration.workerQueueEnqueued;
}
std::string backendMessageOrDefault(const WalletBackendStatus& status,
const std::string& fallback)
{
return status.message.empty() ? fallback : status.message;
}
bool recoveryRequiredByIntegration(const LiteWalletSyncAppRefreshIntegrationResult& integration)
{
return integration.recoveryRequired ||
integration.futureClearRequired ||
integration.futureRescanRequired ||
integration.futureRestartSyncRequired ||
integration.requiresUserAttention;
}
bool cancellationRequiredByIntegration(const LiteWalletSyncAppRefreshIntegrationResult& integration)
{
return integration.cancellationRequired ||
integration.status == LiteWalletSyncAppRefreshIntegrationStatus::WaitingForCancellation;
}
void copyIntegrationSummary(LiteWalletSyncExecutionReadinessResult& result,
const LiteWalletSyncAppRefreshIntegrationResult& integration)
{
result.integrationReport = integration;
result.integrationFeedReady = integration.futureWorkerQueueFeedReady;
result.recoveryRequired = recoveryRequiredByIntegration(integration);
result.cancellationRequired = cancellationRequiredByIntegration(integration);
result.futureClearRequired = integration.futureClearRequired;
result.futureRescanRequired = integration.futureRescanRequired;
result.futureRestartSyncRequired = integration.futureRestartSyncRequired;
result.requiresUserAttention = integration.requiresUserAttention;
result.enablementPlan.startSyncCommand = integration.syncStartPlan.command;
result.enablementPlan.syncStatusCommand = integration.syncStatusPlan.command;
result.enablementPlan.workerQueueLane = integration.futureWorkerQueuePlan.laneName;
result.enablementPlan.route = integration.route;
}
LiteWalletSyncExecutionReadinessResult evaluateRecoveryReadiness(
LiteWalletSyncExecutionReadinessResult result,
const LiteWalletSyncExecutionRecoveryReadinessInput& recovery,
LiteWalletSyncExecutionReadinessOptions options)
{
if (!options.requireRecoveryExecutionReadiness) {
result.recoveryExecutionReady = true;
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::IntegrationFeedNotReady,
"lite sync recovery must complete before sync execution can be enabled");
}
if (result.futureClearRequired && !recovery.clearActionReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::RecoveryClearMissing,
"lite sync recovery requires a future clear action before sync execution");
}
if (result.futureRescanRequired && !recovery.rescanActionReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::RecoveryRescanMissing,
"lite sync recovery requires a future rescan action before sync execution");
}
if (result.futureRestartSyncRequired && !recovery.restartSyncActionReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::RecoveryRestartMissing,
"lite sync recovery requires a future restart-sync action before sync execution");
}
if (result.requiresUserAttention && !recovery.userAttentionPathReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::RecoveryUserAttentionMissing,
"lite sync recovery requires a user-attention path before sync execution");
}
result.recoveryExecutionReady = true;
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery,
LiteWalletSyncExecutionReadinessIssue::IntegrationFeedNotReady,
"lite sync recovery is ready to be modeled but must complete before sync execution");
}
} // namespace
const char* liteWalletSyncExecutionReadinessStatusName(
LiteWalletSyncExecutionReadinessStatus status)
{
switch (status) {
case LiteWalletSyncExecutionReadinessStatus::ReadyToEnableSyncExecution: return "ReadyToEnableSyncExecution";
case LiteWalletSyncExecutionReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild";
case LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact: return "WaitingForArtifact";
case LiteWalletSyncExecutionReadinessStatus::WaitingForBackendLink: return "WaitingForBackendLink";
case LiteWalletSyncExecutionReadinessStatus::WaitingForBridge: return "WaitingForBridge";
case LiteWalletSyncExecutionReadinessStatus::WaitingForIntegration: return "WaitingForIntegration";
case LiteWalletSyncExecutionReadinessStatus::WaitingForExecutionOwner: return "WaitingForExecutionOwner";
case LiteWalletSyncExecutionReadinessStatus::WaitingForCancellation: return "WaitingForCancellation";
case LiteWalletSyncExecutionReadinessStatus::WaitingForShutdown: return "WaitingForShutdown";
case LiteWalletSyncExecutionReadinessStatus::WaitingForRetry: return "WaitingForRetry";
case LiteWalletSyncExecutionReadinessStatus::WaitingForRecovery: return "WaitingForRecovery";
case LiteWalletSyncExecutionReadinessStatus::UnsafePlan: return "UnsafePlan";
}
return "Unknown";
}
const char* liteWalletSyncExecutionReadinessIssueName(
LiteWalletSyncExecutionReadinessIssue issue)
{
switch (issue) {
case LiteWalletSyncExecutionReadinessIssue::FullNodeBuild: return "FullNodeBuild";
case LiteWalletSyncExecutionReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing";
case LiteWalletSyncExecutionReadinessIssue::ArtifactPathMissing: return "ArtifactPathMissing";
case LiteWalletSyncExecutionReadinessIssue::ArtifactMissing: return "ArtifactMissing";
case LiteWalletSyncExecutionReadinessIssue::ArtifactUnreadable: return "ArtifactUnreadable";
case LiteWalletSyncExecutionReadinessIssue::ArtifactNotSdxlCompatible: return "ArtifactNotSdxlCompatible";
case LiteWalletSyncExecutionReadinessIssue::ArtifactSymbolsMissing: return "ArtifactSymbolsMissing";
case LiteWalletSyncExecutionReadinessIssue::ArtifactStringOwnershipUnverified: return "ArtifactStringOwnershipUnverified";
case LiteWalletSyncExecutionReadinessIssue::ArtifactShutdownUnavailable: return "ArtifactShutdownUnavailable";
case LiteWalletSyncExecutionReadinessIssue::BackendNotLinked: return "BackendNotLinked";
case LiteWalletSyncExecutionReadinessIssue::BridgeUnavailable: return "BridgeUnavailable";
case LiteWalletSyncExecutionReadinessIssue::IntegrationRejected: return "IntegrationRejected";
case LiteWalletSyncExecutionReadinessIssue::IntegrationUnsafe: return "IntegrationUnsafe";
case LiteWalletSyncExecutionReadinessIssue::IntegrationFeedNotReady: return "IntegrationFeedNotReady";
case LiteWalletSyncExecutionReadinessIssue::WorkerQueueOwnerMissing: return "WorkerQueueOwnerMissing";
case LiteWalletSyncExecutionReadinessIssue::SyncLoopOwnerMissing: return "SyncLoopOwnerMissing";
case LiteWalletSyncExecutionReadinessIssue::StartSyncExecutionMissing: return "StartSyncExecutionMissing";
case LiteWalletSyncExecutionReadinessIssue::SyncStatusExecutionMissing: return "SyncStatusExecutionMissing";
case LiteWalletSyncExecutionReadinessIssue::CancellationPathMissing: return "CancellationPathMissing";
case LiteWalletSyncExecutionReadinessIssue::CancellationPending: return "CancellationPending";
case LiteWalletSyncExecutionReadinessIssue::ShutdownPathMissing: return "ShutdownPathMissing";
case LiteWalletSyncExecutionReadinessIssue::ShutdownPending: return "ShutdownPending";
case LiteWalletSyncExecutionReadinessIssue::RetryPolicyMissing: return "RetryPolicyMissing";
case LiteWalletSyncExecutionReadinessIssue::RetryLimitReached: return "RetryLimitReached";
case LiteWalletSyncExecutionReadinessIssue::UserVisibleStatusMissing: return "UserVisibleStatusMissing";
case LiteWalletSyncExecutionReadinessIssue::RecoveryClearMissing: return "RecoveryClearMissing";
case LiteWalletSyncExecutionReadinessIssue::RecoveryRescanMissing: return "RecoveryRescanMissing";
case LiteWalletSyncExecutionReadinessIssue::RecoveryRestartMissing: return "RecoveryRestartMissing";
case LiteWalletSyncExecutionReadinessIssue::RecoveryUserAttentionMissing: return "RecoveryUserAttentionMissing";
}
return "Unknown";
}
LiteWalletSyncExecutionReadinessResult evaluateLiteWalletSyncExecutionReadiness(
const LiteWalletSyncExecutionReadinessInput& input,
LiteWalletSyncExecutionReadinessOptions options)
{
LiteWalletSyncExecutionReadinessResult result;
result.capabilities = input.capabilities;
result.backendStatus = input.backend.status;
copyIntegrationSummary(result, input.integrationReport);
if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForLiteBuild,
LiteWalletSyncExecutionReadinessIssue::FullNodeBuild,
"lite sync execution readiness requires a lite build");
}
result.liteBuildAccepted = true;
if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForBackendLink,
LiteWalletSyncExecutionReadinessIssue::LiteBackendCapabilityMissing,
"lite backend capability is not available");
}
if (options.requireValidatedArtifact) {
if (!input.artifact.pathConfigured || input.artifact.artifactPath.empty()) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactPathMissing,
"SDXL-compatible lite backend artifact path is not configured");
}
if (!input.artifact.exists) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactMissing,
"SDXL-compatible lite backend artifact does not exist");
}
if (!input.artifact.readable) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactUnreadable,
"SDXL-compatible lite backend artifact is not readable");
}
if (!input.artifact.sdxlCompatible) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactNotSdxlCompatible,
"lite backend artifact has not been validated as SDXL-compatible");
}
if (!coreArtifactSymbolsReady(input.artifact.symbols)) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactSymbolsMissing,
"lite backend artifact is missing required SDXL lifecycle or execute symbols");
}
if (!input.artifact.symbols.freeString) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactStringOwnershipUnverified,
"lite backend artifact string ownership cleanup is not validated");
}
if (!input.artifact.symbols.shutdown) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForArtifact,
LiteWalletSyncExecutionReadinessIssue::ArtifactShutdownUnavailable,
"lite backend artifact shutdown symbol is not validated");
}
}
result.artifactAccepted = true;
if (options.requireLinkedBackend && !input.backend.linked) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForBackendLink,
LiteWalletSyncExecutionReadinessIssue::BackendNotLinked,
backendMessageOrDefault(input.backend.status, "lite backend is not linked"));
}
result.backendLinkedAccepted = true;
if (options.requireBridgeAvailable && !input.backend.bridgeAvailable) {
const auto message = !input.backend.bridgeUnavailableReason.empty()
? input.backend.bridgeUnavailableReason
: backendMessageOrDefault(input.backend.status, "lite client bridge is unavailable");
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForBridge,
LiteWalletSyncExecutionReadinessIssue::BridgeUnavailable,
message);
}
result.bridgeAccepted = true;
if (integrationReportIsUnsafe(input.integrationReport)) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::UnsafePlan,
LiteWalletSyncExecutionReadinessIssue::IntegrationUnsafe,
"lite sync/app-refresh integration report is not a no-network dry-run report");
}
if (result.recoveryRequired) {
return evaluateRecoveryReadiness(std::move(result), input.recovery, options);
}
if (result.cancellationRequired || input.cancellation.cancellationRequested) {
result.cancellationRequired = true;
if (!input.cancellation.cancellationPathReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForCancellation,
LiteWalletSyncExecutionReadinessIssue::CancellationPathMissing,
"lite sync cancellation path must be ready before sync execution");
}
if (!input.cancellation.cancellationComplete) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForCancellation,
LiteWalletSyncExecutionReadinessIssue::CancellationPending,
"lite sync cancellation must complete before sync execution");
}
}
if (!input.integrationReport.ok) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForIntegration,
LiteWalletSyncExecutionReadinessIssue::IntegrationRejected,
input.integrationReport.error.empty()
? "lite sync/app-refresh integration report is not successful"
: input.integrationReport.error);
}
if (options.requireIntegrationFeedReady && !input.integrationReport.futureWorkerQueueFeedReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForIntegration,
LiteWalletSyncExecutionReadinessIssue::IntegrationFeedNotReady,
"lite sync/app-refresh integration report is not ready to feed future work");
}
result.integrationAccepted = true;
if (options.requireWorkerQueueOwner && !input.ownership.workerQueueOwnerReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForExecutionOwner,
LiteWalletSyncExecutionReadinessIssue::WorkerQueueOwnerMissing,
"lite sync execution requires explicit worker queue ownership");
}
result.workerQueueOwnerAccepted = true;
if (options.requireSyncLoopOwner && !input.ownership.syncLoopOwnerReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForExecutionOwner,
LiteWalletSyncExecutionReadinessIssue::SyncLoopOwnerMissing,
"lite sync execution requires explicit sync loop ownership");
}
result.syncLoopOwnerAccepted = true;
if (options.requireStartSyncExecution && !input.ownership.startSyncExecutionReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForExecutionOwner,
LiteWalletSyncExecutionReadinessIssue::StartSyncExecutionMissing,
"lite sync start execution path is not ready");
}
result.startSyncExecutionReady = true;
if (options.requireSyncStatusPolling && !input.ownership.syncStatusPollingReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForExecutionOwner,
LiteWalletSyncExecutionReadinessIssue::SyncStatusExecutionMissing,
"lite syncstatus polling execution path is not ready");
}
result.syncStatusPollingReady = true;
if (options.requireCancellationPath && !input.cancellation.cancellationPathReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForCancellation,
LiteWalletSyncExecutionReadinessIssue::CancellationPathMissing,
"lite sync cancellation path must be ready before sync execution");
}
result.cancellationReady = true;
if (options.requireShutdownHook && !input.shutdown.shutdownHookReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForShutdown,
LiteWalletSyncExecutionReadinessIssue::ShutdownPathMissing,
"lite sync shutdown hook must be ready before sync execution");
}
if (input.shutdown.shutdownRequested && !input.shutdown.shutdownComplete) {
result.shutdownRequired = true;
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForShutdown,
LiteWalletSyncExecutionReadinessIssue::ShutdownPending,
"lite sync shutdown must complete before new sync execution");
}
result.shutdownReady = true;
if (options.requireRetryPolicy && !input.retry.retryPolicyReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRetry,
LiteWalletSyncExecutionReadinessIssue::RetryPolicyMissing,
"lite sync retry policy must be ready before sync execution");
}
result.retryReady = true;
if (options.requireUserVisibleStatus && !input.ownership.userVisibleStatusReady) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRetry,
LiteWalletSyncExecutionReadinessIssue::UserVisibleStatusMissing,
"lite sync execution requires user-visible status reporting");
}
result.userVisibleStatusReady = true;
if (input.retry.retryRequested) {
result.retryRequired = true;
if (input.retry.maxAttempts > 0 && input.retry.attempt >= input.retry.maxAttempts) {
return stoppedResult(
std::move(result),
LiteWalletSyncExecutionReadinessStatus::WaitingForRetry,
LiteWalletSyncExecutionReadinessIssue::RetryLimitReached,
"lite sync retry limit has been reached");
}
}
result.ok = true;
result.status = LiteWalletSyncExecutionReadinessStatus::ReadyToEnableSyncExecution;
result.syncExecutionCouldStart = true;
result.syncStatusPollingCouldStart = true;
result.executionWouldUseBridge = true;
result.enablementPlan.readyToEnable = true;
result.enablementPlan.startSyncExecutionEnabled = true;
result.enablementPlan.syncStatusPollingEnabled = true;
return result;
}
LiteWalletSyncExecutionReadinessPlanner::LiteWalletSyncExecutionReadinessPlanner(
LiteWalletSyncExecutionReadinessOptions options)
: options_(options)
{
}
LiteWalletSyncExecutionReadinessResult LiteWalletSyncExecutionReadinessPlanner::evaluate(
const LiteWalletSyncExecutionReadinessInput& input) const
{
return evaluateLiteWalletSyncExecutionReadiness(input, options_);
}
} // namespace dragonx::wallet

View File

@@ -0,0 +1,238 @@
#pragma once
#include "lite_wallet_sync_app_refresh_integration.h"
#include "wallet_capabilities.h"
#include <cstddef>
#include <string>
#include <vector>
namespace dragonx::wallet {
enum class LiteWalletSyncExecutionReadinessStatus {
ReadyToEnableSyncExecution,
WaitingForLiteBuild,
WaitingForArtifact,
WaitingForBackendLink,
WaitingForBridge,
WaitingForIntegration,
WaitingForExecutionOwner,
WaitingForCancellation,
WaitingForShutdown,
WaitingForRetry,
WaitingForRecovery,
UnsafePlan,
};
enum class LiteWalletSyncExecutionReadinessIssue {
FullNodeBuild,
LiteBackendCapabilityMissing,
ArtifactPathMissing,
ArtifactMissing,
ArtifactUnreadable,
ArtifactNotSdxlCompatible,
ArtifactSymbolsMissing,
ArtifactStringOwnershipUnverified,
ArtifactShutdownUnavailable,
BackendNotLinked,
BridgeUnavailable,
IntegrationRejected,
IntegrationUnsafe,
IntegrationFeedNotReady,
WorkerQueueOwnerMissing,
SyncLoopOwnerMissing,
StartSyncExecutionMissing,
SyncStatusExecutionMissing,
CancellationPathMissing,
CancellationPending,
ShutdownPathMissing,
ShutdownPending,
RetryPolicyMissing,
RetryLimitReached,
UserVisibleStatusMissing,
RecoveryClearMissing,
RecoveryRescanMissing,
RecoveryRestartMissing,
RecoveryUserAttentionMissing,
};
struct LiteWalletSdxlArtifactSymbolsInput {
bool walletExists = false;
bool initializeNew = false;
bool initializeNewFromPhrase = false;
bool initializeExisting = false;
bool execute = false;
bool freeString = false;
bool checkServerOnline = false;
bool shutdown = false;
};
struct LiteWalletSdxlArtifactInput {
bool pathConfigured = false;
bool exists = false;
bool readable = false;
bool sdxlCompatible = false;
std::string artifactPath;
std::string versionLabel;
LiteWalletSdxlArtifactSymbolsInput symbols;
};
struct LiteWalletLinkedBackendReadinessInput {
bool linked = false;
bool bridgeAvailable = false;
WalletBackendStatus status;
std::string bridgeUnavailableReason;
};
struct LiteWalletSyncExecutionOwnershipInput {
bool workerQueueOwnerReady = false;
bool syncLoopOwnerReady = false;
bool startSyncExecutionReady = false;
bool syncStatusPollingReady = false;
bool userVisibleStatusReady = false;
};
struct LiteWalletSyncExecutionCancellationReadinessInput {
bool cancellationPathReady = false;
bool cancellationRequested = false;
bool cancellationComplete = false;
};
struct LiteWalletSyncExecutionShutdownReadinessInput {
bool shutdownHookReady = false;
bool shutdownRequested = false;
bool shutdownComplete = false;
};
struct LiteWalletSyncExecutionRetryReadinessInput {
bool retryPolicyReady = false;
bool retryRequested = false;
std::size_t attempt = 0;
std::size_t maxAttempts = 0;
};
struct LiteWalletSyncExecutionRecoveryReadinessInput {
bool clearActionReady = false;
bool rescanActionReady = false;
bool restartSyncActionReady = false;
bool userAttentionPathReady = false;
};
struct LiteWalletSyncExecutionReadinessInput {
WalletCapabilities capabilities;
LiteWalletSdxlArtifactInput artifact;
LiteWalletLinkedBackendReadinessInput backend;
LiteWalletSyncExecutionOwnershipInput ownership;
LiteWalletSyncExecutionCancellationReadinessInput cancellation;
LiteWalletSyncExecutionShutdownReadinessInput shutdown;
LiteWalletSyncExecutionRetryReadinessInput retry;
LiteWalletSyncExecutionRecoveryReadinessInput recovery;
LiteWalletSyncAppRefreshIntegrationResult integrationReport;
};
struct LiteWalletSyncExecutionReadinessOptions {
bool requireLiteBuild = true;
bool requireLiteBackendCapability = true;
bool requireValidatedArtifact = true;
bool requireLinkedBackend = true;
bool requireBridgeAvailable = true;
bool requireIntegrationFeedReady = true;
bool requireWorkerQueueOwner = true;
bool requireSyncLoopOwner = true;
bool requireStartSyncExecution = true;
bool requireSyncStatusPolling = true;
bool requireCancellationPath = true;
bool requireShutdownHook = true;
bool requireRetryPolicy = true;
bool requireUserVisibleStatus = true;
bool requireRecoveryExecutionReadiness = true;
};
struct LiteWalletSyncExecutionEnablementPlan {
bool readyToEnable = false;
bool startSyncExecutionEnabled = false;
bool syncStatusPollingEnabled = false;
bool workerQueueEnqueued = false;
std::string startSyncCommand;
std::string syncStatusCommand;
std::string workerQueueLane;
LiteWalletRefreshRouteKind route = LiteWalletRefreshRouteKind::Unavailable;
};
struct LiteWalletSyncExecutionReadinessIssueInfo {
LiteWalletSyncExecutionReadinessIssue issue = LiteWalletSyncExecutionReadinessIssue::FullNodeBuild;
std::string message;
};
struct LiteWalletSyncExecutionReadinessResult {
bool ok = false;
bool dryRunOnly = true;
bool noNetwork = true;
bool noBridgeCalls = true;
bool noSyncStarted = true;
bool noSyncStatusPolled = true;
bool noWalletPersistence = true;
bool stateMutationAllowed = false;
bool stateMutated = false;
bool walletStateWritten = false;
bool workerQueueEnqueued = false;
bool liteBuildAccepted = false;
bool artifactAccepted = false;
bool backendLinkedAccepted = false;
bool bridgeAccepted = false;
bool integrationAccepted = false;
bool workerQueueOwnerAccepted = false;
bool syncLoopOwnerAccepted = false;
bool startSyncExecutionReady = false;
bool syncStatusPollingReady = false;
bool cancellationReady = false;
bool shutdownReady = false;
bool retryReady = false;
bool userVisibleStatusReady = false;
bool recoveryExecutionReady = false;
bool syncExecutionCouldStart = false;
bool syncStatusPollingCouldStart = false;
bool executionWouldUseBridge = false;
bool integrationFeedReady = false;
bool recoveryRequired = false;
bool cancellationRequired = false;
bool shutdownRequired = false;
bool retryRequired = false;
bool futureClearRequired = false;
bool futureRescanRequired = false;
bool futureRestartSyncRequired = false;
bool requiresUserAttention = false;
LiteWalletSyncExecutionReadinessStatus status = LiteWalletSyncExecutionReadinessStatus::WaitingForLiteBuild;
WalletCapabilities capabilities;
WalletBackendStatus backendStatus;
LiteWalletSyncAppRefreshIntegrationResult integrationReport;
LiteWalletSyncExecutionEnablementPlan enablementPlan;
std::vector<LiteWalletSyncExecutionReadinessIssueInfo> issues;
std::string error;
};
const char* liteWalletSyncExecutionReadinessStatusName(
LiteWalletSyncExecutionReadinessStatus status);
const char* liteWalletSyncExecutionReadinessIssueName(
LiteWalletSyncExecutionReadinessIssue issue);
LiteWalletSyncExecutionReadinessResult evaluateLiteWalletSyncExecutionReadiness(
const LiteWalletSyncExecutionReadinessInput& input,
LiteWalletSyncExecutionReadinessOptions options = {});
class LiteWalletSyncExecutionReadinessPlanner {
public:
explicit LiteWalletSyncExecutionReadinessPlanner(
LiteWalletSyncExecutionReadinessOptions options = {});
LiteWalletSyncExecutionReadinessResult evaluate(
const LiteWalletSyncExecutionReadinessInput& input) const;
private:
LiteWalletSyncExecutionReadinessOptions options_;
};
} // namespace dragonx::wallet

View File

@@ -0,0 +1,93 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "wallet_capabilities.h"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace dragonx {
namespace wallet {
enum class WalletBackendState {
Unavailable,
Disconnected,
Connecting,
Syncing,
Ready,
Error
};
struct WalletBackendStatus {
WalletBackendState state = WalletBackendState::Unavailable;
std::string message;
std::optional<int> walletHeight;
std::optional<int> chainHeight;
double syncProgress = 0.0;
};
struct WalletBackendResult {
bool ok = false;
std::string error;
};
struct WalletOpenRequest {
std::string walletPath;
std::string serverUrl;
bool restoreFromSeed = false;
};
struct WalletSendRequest {
std::string fromAddress;
std::string toAddress;
double amount = 0.0;
double fee = 0.0;
std::string memo;
};
struct WalletSendResult {
bool accepted = false;
std::string txid;
std::string error;
};
class WalletBackend {
public:
virtual ~WalletBackend() = default;
virtual WalletBackendKind kind() const = 0;
virtual WalletCapabilities capabilities() const = 0;
virtual WalletBackendStatus status() const = 0;
};
class FullNodeWalletBackend : public WalletBackend {
public:
~FullNodeWalletBackend() override = default;
};
class LiteWalletBackend : public WalletBackend {
public:
~LiteWalletBackend() override = default;
virtual WalletBackendResult setServer(const std::string& serverUrl) = 0;
virtual WalletBackendResult openWallet(const WalletOpenRequest& request) = 0;
virtual WalletBackendResult startSync() = 0;
virtual WalletBackendResult cancelSync() = 0;
virtual WalletSendResult sendTransaction(const WalletSendRequest& request) = 0;
};
class WalletBackendRouter {
public:
virtual ~WalletBackendRouter() = default;
virtual WalletBackend& activeBackend() = 0;
virtual const WalletBackend& activeBackend() const = 0;
};
} // namespace wallet
} // namespace dragonx

View File

@@ -0,0 +1,179 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#ifndef DRAGONX_LITE_BUILD
#define DRAGONX_LITE_BUILD 0
#endif
#ifndef DRAGONX_ENABLE_EMBEDDED_DAEMON
#define DRAGONX_ENABLE_EMBEDDED_DAEMON 1
#endif
#ifndef DRAGONX_ENABLE_LITE_BACKEND
#define DRAGONX_ENABLE_LITE_BACKEND 0
#endif
namespace dragonx {
namespace wallet {
enum class WalletBuildKind {
FullNode,
Lite
};
enum class WalletBackendKind {
FullNodeRpc,
LiteClient
};
enum class WalletUiSurface {
Overview,
Send,
Receive,
History,
Mining,
Market,
Console,
Peers,
Explorer,
Settings,
BootstrapDownload,
SetupWizard,
NodeSettings
};
struct WalletCapabilities {
WalletBuildKind buildKind = WalletBuildKind::FullNode;
WalletBackendKind backendKind = WalletBackendKind::FullNodeRpc;
bool fullNodeRpcAvailable = true;
bool embeddedDaemonAvailable = true;
bool fullNodePagesAvailable = true;
bool fullNodeLifecycleActionsAvailable = true;
bool soloMiningAvailable = true;
bool poolMiningAvailable = true;
bool liteBackendAvailable = false;
bool lightwalletdNetworkAvailable = false;
bool liteWalletLifecycleAvailable = false;
bool liteWalletSyncAvailable = false;
bool liteWalletSendAvailable = false;
};
constexpr bool isLiteBuild(WalletBuildKind buildKind)
{
return buildKind == WalletBuildKind::Lite;
}
constexpr bool isLiteBuild(const WalletCapabilities& capabilities)
{
return isLiteBuild(capabilities.buildKind);
}
constexpr bool isFullNodeBuild(WalletBuildKind buildKind)
{
return buildKind == WalletBuildKind::FullNode;
}
constexpr WalletCapabilities makeWalletCapabilities(
WalletBuildKind buildKind,
bool embeddedDaemonCompiled,
bool liteBackendLinked)
{
const bool liteBuild = isLiteBuild(buildKind);
const bool fullNodeBuild = !liteBuild;
const bool liteBackendAvailable = liteBuild && liteBackendLinked;
return WalletCapabilities{
buildKind,
liteBuild ? WalletBackendKind::LiteClient : WalletBackendKind::FullNodeRpc,
fullNodeBuild,
fullNodeBuild && embeddedDaemonCompiled,
fullNodeBuild,
fullNodeBuild,
fullNodeBuild,
true,
liteBackendAvailable,
liteBackendAvailable,
liteBackendAvailable,
liteBackendAvailable,
liteBackendAvailable
};
}
constexpr WalletBuildKind currentWalletBuildKind()
{
return DRAGONX_LITE_BUILD ? WalletBuildKind::Lite : WalletBuildKind::FullNode;
}
constexpr WalletCapabilities currentWalletCapabilities()
{
return makeWalletCapabilities(
currentWalletBuildKind(),
DRAGONX_ENABLE_EMBEDDED_DAEMON != 0,
DRAGONX_ENABLE_LITE_BACKEND != 0);
}
constexpr bool supportsEmbeddedDaemon(const WalletCapabilities& capabilities)
{
return capabilities.embeddedDaemonAvailable;
}
constexpr bool supportsFullNodeLifecycleActions(const WalletCapabilities& capabilities)
{
return capabilities.fullNodeLifecycleActionsAvailable;
}
constexpr bool supportsSoloMining(const WalletCapabilities& capabilities)
{
return capabilities.soloMiningAvailable;
}
constexpr bool supportsPoolMining(const WalletCapabilities& capabilities)
{
return capabilities.poolMiningAvailable;
}
constexpr bool supportsLiteBackend(const WalletCapabilities& capabilities)
{
return capabilities.liteBackendAvailable;
}
constexpr bool supportsWalletDataBackend(const WalletCapabilities& capabilities)
{
return capabilities.fullNodeRpcAvailable || capabilities.liteWalletSyncAvailable;
}
constexpr bool isUiSurfaceAvailable(const WalletCapabilities& capabilities,
WalletUiSurface surface)
{
switch (surface) {
case WalletUiSurface::Console:
case WalletUiSurface::Peers:
case WalletUiSurface::Explorer:
return capabilities.fullNodePagesAvailable;
case WalletUiSurface::BootstrapDownload:
case WalletUiSurface::SetupWizard:
case WalletUiSurface::NodeSettings:
return capabilities.fullNodeLifecycleActionsAvailable;
default:
return true;
}
}
constexpr bool uiSurfaceNeedsWalletData(WalletUiSurface surface)
{
switch (surface) {
case WalletUiSurface::Overview:
case WalletUiSurface::Send:
case WalletUiSurface::Receive:
case WalletUiSurface::History:
return true;
default:
return false;
}
}
} // namespace wallet
} // namespace dragonx