Files
ObsidianDragon/src/wallet/lite_wallet_lifecycle_service.h
DanS b3c2282b53 feat(lite): runtime kill-switch + staged-rollout gate (M5b)
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>
2026-06-06 12:01:08 -05:00

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