Adds a fail-open, local-only gate that decides whether the lite wallet may run,
so a post-release issue can disable it and rollout can be staged — without any
phone-home (privacy posture: no runtime network fetch; the per-install rollout
bucket is a hashed, never-transmitted local id).
- wallet/lite_rollout_policy.{h,cpp}: a pure decision core. Order — emergency env
kill-switch (absolute) -> local override -> manifest gates (global enable /
version floor-ceiling / blocklist / staged-rollout permille) -> fail-open allow.
Plus a JSON manifest loader (missing/invalid -> fail-open) and FNV-1a bucketing.
- Threads the decision through LiteWalletController -> LiteWalletLifecycleService:
new availability() reason RolloutDisabled blocks create/open/restore and surfaces
the gate's user-facing message via the lifecycle status.
- App::rebuildLiteWallet() resolves it from: DRAGONX_LITE_KILL_SWITCH (env), the
lite_rollout setting (auto/force_on/force_off), and a locally-cached manifest at
<config-dir>/lite_rollout.json. install id generated once via libsodium.
- Settings: persist lite_rollout override + the install id.
A signed remote fetcher can populate the manifest cache later without touching the
policy. Unit-tested (version compare, bucketing, override/env precedence, manifest
gates, staged rollout, loader fail-open, controller integration) and runtime-verified
on Linux (env kill-switch, manifest disable, control sync). Both variants build;
full suite passes; hygiene clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
5.9 KiB
C++
158 lines
5.9 KiB
C++
// 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,
|
|
RolloutDisabled // runtime kill-switch / staged-rollout gate (see lite_rollout_policy.h)
|
|
};
|
|
|
|
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;
|
|
bool rolloutBlocked = false; // runtime kill-switch / staged-rollout gate is blocking
|
|
std::string rolloutMessage; // user-facing reason when rolloutBlocked
|
|
};
|
|
|
|
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:
|
|
// bridge is NON-OWNING (the controller owns the single shared LiteClientBridge);
|
|
// it must outlive this service. The lite backend is a global singleton and every
|
|
// LiteClientBridge shuts it down on destruction, so services must not own one.
|
|
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_ = nullptr; // non-owning; owned by LiteWalletController
|
|
LiteWalletLifecycleOptions options_;
|
|
};
|
|
|
|
} // namespace wallet
|
|
} // namespace dragonx
|